0Pricing
tRPC End-to-End Type Safe APIs · 강의

tRPC를 활용한 파일 업로드

multipart/form-data와 적절한 파서를 사용하여 tRPC 변형에 파일 업로드 기능을 연동합니다.

tRPC를 활용한 파일 업로드은(는) CoddyKit의 무료 tRPC End-to-End Type Safe APIs 강의입니다. 이것은 4개 중 3번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 tRPC End-to-End Type Safe APIs 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. tRPC End-to-End Type Safe APIs 강의에는 총 4개의 강의가 포함되어 있습니다.

이 강의의 일부는 아직 번역되지 않았으며 영어로 표시됩니다.

Intro to File Uploads

Welcome to handling file uploads with tRPC! Unlike simple JSON data, files (like images or documents) require a special approach because they are often binary data.

tRPC itself focuses on type-safe API definitions. It doesn't natively handle the low-level parsing of file uploads. Instead, it integrates seamlessly with the underlying HTTP server framework, like Express or Next.js, to manage this.

Understanding multipart/form-data

When you upload a file via a web form, the data is typically sent using the multipart/form-data encoding type. This is different from application/json, which is common for most tRPC requests.

  • multipart/form-data: Allows sending both text fields and binary files in a single request.
  • application/json: Primarily for structured text data, not ideal for large binary files.

Your server needs a special 'parser' to correctly interpret multipart/form-data requests.

Server-side Setup: Express & Parsers

To integrate file uploads with tRPC, we'll use an Express server as our backend. Express is a popular Node.js framework that can easily host tRPC.

For parsing multipart/form-data, we'll use a middleware like express-fileupload. This middleware will process the incoming request and make the uploaded files accessible on the req object.

Basic Express Server with File Middleware

First, let's set up a basic Express server and integrate the express-fileupload middleware. This makes files available on the req object for later use.

Run npm install express express-fileupload in your project.

Try running this example:

const express = require('express');
const fileUpload = require('express-fileupload');
const app = express();
const port = 3000;

// Enable file upload middleware
app.use(fileUpload());

app.get('/', (req, res) => {
  res.send('Express server running!');
});

// Simple endpoint to show file access
app.post('/upload-test', (req, res) => {
  if (!req.files || Object.keys(req.files).length === 0) {
    return res.status(400).send('No files uploaded.');
  }
  
  // 'myFile' refers to the name attribute in the HTML input
  let uploadedFile = req.files.myFile;
  
  console.log('File received:', uploadedFile.name);
  res.send(`File '${uploadedFile.name}' received.`);
});

app.listen(port, () => {
  console.log(`Server on http://localhost:${port}`);
});

Integrating tRPC into the Server

Now, let's add tRPC on top of our Express server. It's crucial that the express-fileupload middleware runs before the tRPC middleware. This ensures that req.files is populated before tRPC's createContext function is called.

Run npm install @trpc/server @trpc/express zod.

import express from 'express';
import fileUpload from 'express-fileupload';
import * as trpcExpress from '@trpc/express';
import { initTRPC } from '@trpc/server';

// Initialize tRPC (we'll define context and router later)
const t = initTRPC.context<any>().create();
const appRouter = t.router({}); // Empty router for now

// tRPC context function
function createContext({ req, res }: { req: express.Request, res: express.Response }) {
  return { req, res, uploadedFiles: req.files }; // Files are here!
}

const app = express();
const port = 3000;

// 1. Enable file upload middleware FIRST
app.use(fileUpload());

// 2. Add tRPC middleware (after fileUpload)
app.use(
  '/trpc',
  trpcExpress.createExpressMiddleware({
    router: appRouter,
    createContext,
  })
);

app.get('/', (req, res) => {
  res.send('tRPC file upload server running!');
});

app.listen(port, () => {
  console.log(`Server on http://localhost:${port}`);
});

Defining tRPC Context with File Access

As shown in the previous example, the express-fileupload middleware adds a files property to the express.Request object. By returning req.files in our createContext function, we make the uploaded files easily accessible within any tRPC procedure.

We've named this property uploadedFiles in our context for clarity.

import express from 'express';
import type { UploadedFile } from 'express-fileupload';

// Extend Express Request type (in a real project, this would be in a types file)
declare global {
  namespace Express {
    interface Request {
      files?: { [key: string]: UploadedFile | UploadedFile[] };
    }
  }
}

export function createContext({
  req,
  res,
}: { req: express.Request; res: express.Response }) {
  return {
    req,
    res,
    // This makes the files available in `ctx.uploadedFiles`
    uploadedFiles: req.files,
  };
}

Building the File Upload Mutation

Now we can define a tRPC mutation that uses the uploadedFiles from our context. We'll use Zod for basic input validation (e.g., a description for the file).

  • Access ctx.uploadedFiles.
  • Retrieve the specific file by its field name (e.g., 'myFile').
  • Use the file's mv() method to save it to disk.
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
import type { UploadedFile } from 'express-fileupload';

// Assuming 't' is initialized with context containing 'uploadedFiles'
const t = initTRPC.context<any>().create(); // Replace 'any' with your actual context type

export const appRouter = t.router({
  uploadFile: t.procedure
    .input(z.object({
      description: z.string().optional(),
    }))
    .mutation(async ({ ctx, input }) => {
      const { uploadedFiles } = ctx;

      if (!uploadedFiles || Object.keys(uploadedFiles).length === 0) {
        throw new Error('No files found in request.');
      }

      // Access the file by the name given in FormData (e.g., 'myFile')
      const file = uploadedFiles.myFile as UploadedFile;

      if (!file) {
        throw new Error('File field "myFile" is missing.');
      }

      const savePath = `./uploads/${file.name}`; // Store temporarily
      await file.mv(savePath); // Move file to destination

      return {
        fileName: file.name,
        size: file.size,
        description: input.description || 'No description',
        message: 'File uploaded successfully!',
      };
    }),
});

Client-side: Creating FormData

On the client, you'll typically use an HTML <input type="file"> element to allow users to select a file. To send this file, you'll create a FormData object.

  • FormData: A web API that lets you easily construct key-value pairs representing form fields and their values, including files.
  • Use formData.append('fieldName', value) to add data. For files, the value is the File object itself.
<!-- Example HTML snippet -->
<input type="file" id="fileInput" />
<script>
  const fileInput = document.getElementById('fileInput');
  
  function prepareFormData() {
    const file = fileInput.files[0];
    if (!file) {
      console.log('No file selected.');
      return null;
    }

    const formData = new FormData();
    // 'myFile' must match the field name expected by the server
    formData.append('myFile', file);
    formData.append('description', 'A document from the user.');

    console.log('FormData prepared!');
    return formData;
  }
</script>

Sending Files to the tRPC Endpoint

The standard tRPC client library isn't designed to send multipart/form-data directly for mutations. Instead, we typically use the native fetch API to send the FormData object to our tRPC endpoint.

The endpoint path will be /trpc/yourProcedureName, and the method will be POST.

Try running this example (ensure your server from Scene 5 & 7 is running):

<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
  <title>Upload File</title>
</head>
<body>
  <input type="file" id="fileInput" />
  <button onclick="uploadFile()">Upload</button>

  <script>
    async function uploadFile() {
      const fileInput = document.getElementById('fileInput');
      const file = fileInput.files[0];

      if (!file) {
        alert('Please select a file first.');
        return;
      }

      const formData = new FormData();
      formData.append('myFile', file); // 'myFile' matches server's expected name
      formData.append('description', 'A file from client!');

      try {
        const response = await fetch('http://localhost:3000/trpc/uploadFile', {
          method: 'POST',
          body: formData,
        });

        const result = await response.json();
        if (response.ok) {
          alert('Upload successful: ' + JSON.stringify(result));
        } else {
          alert('Upload failed: ' + JSON.stringify(result));
        }
      } catch (error) {
        console.error('Error uploading file:', error);
        alert('An error occurred during upload.');
      }
    }
  </script>
</body>
</html>

Quick Check

Which of the following statements are true about handling file uploads with tRPC?

Recap & Next Steps

You've learned how to integrate file upload functionality into your tRPC application!

  • File uploads use multipart/form-data, requiring server-side parsing.
  • express-fileupload middleware on an Express server handles this parsing.
  • By applying the file upload middleware before tRPC middleware, req.files becomes available in your tRPC context.
  • Your tRPC mutation can then access and save the uploaded files.
  • On the client, use FormData and the native fetch API to send files to your tRPC endpoint.

Remember to consider robust file storage solutions (e.g., cloud storage like S3) and proper error handling for production applications.

자주 묻는 질문

“tRPC를 활용한 파일 업로드” 강의는 무료인가요?

네 — “tRPC를 활용한 파일 업로드” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 tRPC End-to-End Type Safe APIs 강의 전체를 잠금 해제할 수 있습니다. tRPC End-to-End Type Safe APIs 강의에는 총 4개의 강의가 포함되어 있습니다.

“tRPC를 활용한 파일 업로드”에서 뭘 배우나요?

multipart/form-data와 적절한 파서를 사용하여 tRPC 변형에 파일 업로드 기능을 연동합니다. 브라우저에서 직접 실행하는 실습 코드로 tRPC End-to-End Type Safe APIs을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.

tRPC End-to-End Type Safe APIs을(를) 시작하는 데 경험이 필요한가요?

사전 경험은 필요하지 않습니다. CoddyKit의 tRPC End-to-End Type Safe APIs은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 3번째 강의입니다.

“tRPC를 활용한 파일 업로드” 강의는 얼마나 걸리나요?

대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.

이 tRPC End-to-End Type Safe APIs 강의에서 코드를 작성하고 실행할 수 있나요?

네. 모든 tRPC End-to-End Type Safe APIs 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. 효율적인 요청 일괄 처리
  2. 낙관적 업데이트 구현
  3. tRPC를 활용한 파일 업로드
  4. 무한 질의와 커서 기반 페이지 매김
← tRPC End-to-End Type Safe APIs(으)로 돌아가기