Programming/Nextjs

[Nextjs] API 기능을 통한 파일 업로드 처리

minarae7 2023. 2. 9. 23:31
728x90
반응형

회사에서 Frontend 개발시에 nextjs를 사용하고 있다. 지금까지 React + nextjs를 공부하고 개발하면서 여러 고비를 넘기면서 개발을 이어왔는데 다시 한 가지 난제에 부딪히게 되었다.

nextjs는 SSR(Server Side Rendering)을 지원해주는 프레임워크이지만 어쨌든 이미 로딩된 페이지 안에서의 동작은 브라우저에서 처리되기 때문에 비동기로 호출되는 데이터에 대한 정보는 서버에서는 잡을 수가 없다. 그래서 Nextjs에서는 Page 디렉토리 안에 api로 된 페이지에 대해서는 특별히 서버에서 직접 처리되도록 하여서 서버에서 처리할 수 있도록 기능을 제공한다. 자세한 내용은 아래 nextjs 문서에서 확인할 수 있다.

728x90
 

API Routes: Introduction | Next.js

Next.js supports API Routes, which allow you to build your API without leaving your Next.js app. Learn how it works here.

nextjs.org

반응형

회사에서 개발할 때는 모든 비동기 api에 대해서 nextjs의 api-route를 이용해서 request와 response에 대한 값을 로그로 남기기 위해서 사용하고 있다.

근데 여기서 문제가 발생했다. POST나 PUT일 경우 지금까지는 http-method를 통해서 데이터를 잡았다. 이 때 request body는 json으로 처리하도록 하였다. 이렇게 처리했기 때문에 지금까지는 별 문제없이 처리가 되었다.

이번에 발생한 문제는 파일 업로드를 해야한다는 것이다. 파일 전송시에는 POST에서도 content-type를 따로 지정해서 보내야하기 때문에 그대로 json을 사용할 수 없고 Form 데이터로 처리해야 하는데 이게 잘 안되었다. 수없는 삽질 끝에 문제의 원인을 찾아냈다.

브라우저에서 데이터를 보낼 때 FormData로 묶어서 정상적으로 전송이 되는데 Backend에 오면 데이터가 이상하게 잡히는 것을 확인하였다. 분명히 데이터 자체에서는 문제가 안 보이는데 Fastapi api에서 처리가 안되는 것을 확인했다. 어떤 문제인지 수없이 테스트해본 결과 문제가 되는 구간이 api-route를 거치면서 form 데이터가 손상되는 것을 확인하였다.

이를 위한 해결책을 찾다가 좋은 솔루션을 찾아서 기록을 위해서 여기에 남겨두도록 한다.

우선 해결을 위해서 다음과 같이 코드를 작성하였다.

반응형
import formidable from "formidable";
import FormData from "form-data";
import fs from "fs";
import axios from "axios";

export const config = {
  api: {
    bodyParser: false,
  }
};

export default async function (req, res) {
  const url = "http://test.co.kr";
  try {
    if (req.method === "POST") {
      // file 업로드 처리
      const {fileData, fields} = await new Promise((resolve, reject) {
        const form = new formidable.IncomingForm({
          maxFiles: 5 * 1024 * 1024,  // 최대 파일 크기 지정
          keepExtension: true,
        });
        
        form.parse(req, (error, fields, files) => {
          if (error) return reject(error);
          return resolve({
            fileData: files,
            fields: fields,
          });
        });
      });
      
      const formData = new FormData();
      const image = fileData.image; // 업로드된 파일의 post 항목의 이름이 image 임.
      const readStream = fs.createReadStream(image.filepath);  // 업로드된 파일을 버퍼로 읽음
      
      formData.append("image", readStream); // formData에 읽은 파일을 추가함
      for (const key in fields) {
        // 다른 필드가 있으면 역시 formData에 추가
        formData.append(key, fields[key];
      }
      
      // 데이터를 처리할 서버로 데이터를 다시 전달
      const result = await axios.post(url, formData);
      res.status(200).json(result);
    } else {
      // 지원하지 않는 http method에 대한 예외처리
      res.statue(200).json({
        result: false,
        message: `Do not support to ${req.method}`;
      });
    }
  } catch (error) {
    res
      .status(error.response?error.response.status : 500)
      .send(error.response ? error.response.data : error.message);
  }
}

 

반응형

 

대충 해결책은 api-route 페이지에서 FormData를 새로 생성하고 이 FormData를 백엔드 서버로 전송하도록 하는 것이다. 여기서 핵심은 두 가지이다.

1. bodyParser를 false로 지정하는 것이다. 이렇게 함으로 nextjs 자체에서 request body에 대한 처리를 하는 것을 막는다.

2. 새로운 FormData를 생성하기 위해서 formidable 라이브러리를 통해서 request body를 처리하여서 파일 부분과 다른 필드들을 분리해서 변수에 담는다.

nextjs에서 사용하는 bodyParser 옵션 및 다른 옵션에 대해서 아래 페이지에서 확인할 수 있다.

728x90
 

API Routes: Request Helpers | Next.js

API Routes provide built-in request helpers that parse the incoming request. Learn more about them here.

nextjs.org

이렇게 처리하면 nextjs의 api-route를 거쳐가면서도 파일의 손상없이 정상적으로 Backend 서버로 전송되는 것을 확인할 수 있다.

해당 코드는 아래 사이트를 참조하였으며 실제 사용하는 코드를 수정해서 내부 라이브러리를 호출하는 부분을 제외하고 작성한 것이다.

코드의 오류 또는 문의 사항은 댓글로 남겨주시면 처리하도록 하겠습니다.

728x90
 

폴시랩

자바스트립트, PHP, 워드프레스, HTML, CSS, 팁, 가이드, 일상이야기.

falsy.me

 

728x90
반응형