next.js 13, multer, s3를 이용한 회원가입 form 작성

kyubhin Han·2023년 2월 20일
4

bookmore

목록 보기
2/2

next.js 13에서 form 데이터를 이용하여 이미지 데이터 및 텍스트 데이터를 처리하고 이를 S3 Bucket에 올리는 부분을 자세히 작성해보겠습니다.

블로그에 관련 글들이 많이 존재하는데, 그대로 따라하니 모듈 업데이트에 따른 에러가 나는 부분이 있어서 이를 최신 방법으로 다시 정리해보고자 합니다.

참고 자료

S3 bucket 생성
S3Client Error handling
multer 공식 문서

처리 프로세스 모식도

처리 프로세스 상세

1. Form 데이터 전송

회원 가입 처리를 하기 위해서 먼저 회원이 필요한 회원 정보를 담은 form 데이터를 전송해주어야 합니다. 이번 서비스에서는 유저의 프로필 이미지, 이메일, 닉네임이 필요하기 때문에 각각을 form 데이터에 넣어 전송해주었습니다. 이때, 유저가 만약 카카오 등록된 기본 프로필 이미지를 사용하면 profiileImageUrl에 해당되는 url을 넣어주었고, 별도로 프로필 이미지를 등록한 경우 해당 값을 빈 값으로 넣어주었습니다.

위 기능은 주요 기능이 아니기 때문에 구현한 코드만 간단하게 첨부하겠습니다.

//app/signup/page.tsx

"use client";

import React, { useState } from "react";
import { useSearchParams } from "next/navigation";
import axios from "axios";

const Page = () => {
 const searchParams = useSearchParams();
 const email = searchParams.get("email");
 const profileImage = searchParams.get("profileImage");
 const [nickname, setNickname] = useState("");
 const [nicknameCheck, setNicknameCheck] = useState("");
 const [imageFile, setImageFile] = useState<File>();

 const handleNicknameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
   setNickname(e.target.value);
 };

 // 닉네임 중복 여부를 파악해줌
 const handleNicknameCheck = async () => {
   const url = "http://localhost:3000/api/signup/nicknamecheck";
   const { data } = await axios.post(url, {
     nickname,
   });
   const { check } = data;
   if (check) {
     setNicknameCheck("possible");
   } else {
     setNicknameCheck("impossible");
   }
 };

 // 회원 가입 요청이 제출되었을 때 처리해줌
 const handleSignupSubmit = (e: React.FormEvent<HTMLFormElement>) => {
   e.preventDefault();

   const targetData = new FormData();
   targetData.append("profileImage", imageFile as File);
   targetData.append("email", String(email));
   targetData.append("nickname", String(nickname));
   if (!imageFile) {
     targetData.append("profileImageUrl", String(profileImage));
   }

   // 전송
   const url = "http://localhost:3000/api/signup";
   axios.post(url, targetData).then((res) => {
     console.log(res);
   });
 };

 // 사진이 입력되었을 때 처리를 해줌
 const handleImageSubmit = (e: React.ChangeEvent<HTMLInputElement>) => {
   if (e.target.files !== null) {
     setImageFile(e.target.files[0]);
   }
 };

 return (
   <div>
     <form onSubmit={handleSignupSubmit}>
       <section>
         <label htmlFor="signup-email">이메일</label>
         <div id="signup-email">{email}</div>
       </section>
       <section>
         <label htmlFor="signup-nickname">닉네임</label>
         <input
           id="signup-nickname"
           type="text"
           value={nickname}
           onChange={handleNicknameChange}
           onBlur={handleNicknameCheck}
         />
         {nicknameCheck === "possible" && <p>사용가능합니다.</p>}
         {nicknameCheck === "impossible" && <p>사용 불가능합니다.</p>}
       </section>
       <section>
         <label htmlFor="signup-profileimage">프로필 이미지</label>
         <input
           id="signup-profileimage"
           type="file"
           accept="image/*"
           onChange={handleImageSubmit}
         />
       </section>
       <button type="submit"> 제출</button>
     </form>
   </div>
 );
};

export default Page;

2. 이미지 데이터 저장

구현하면서 가장 많이 공부하고 헤맸던 부분입니다. 따라서 가장 자세히 작성해보겠습니다.

먼저 이미지 데이터를 S3 bucket에 넣어주기 위해 bucket을 만들고 기본적인 설정을 해주어야 합니다. 이는 상세하게 정리한 사이트가 있기 때문에 아래의 사이트를 참고하여 버킷을 만들어주시면 됩니다.
S3 bucket 생성

나머지 자세한 내용은 작성한 코드를 보면서 설명 드리겠습니다.


import type { NextApiRequest, NextApiResponse } from "next";
import multer from "multer";
import multerS3 from "multer-s3";
import nextConnect from "next-connect";
import { S3Client } from "@aws-sdk/client-s3";
import prisma from "lib/prisma";
import dotenv from "dotenv";
dotenv.config();

// 1) s3 버킷에 대한 정보를 설정해줌
const s3 = new S3Client({
  credentials: {
    accessKeyId: String(process.env.AWS_ACCESS_KEY_ID),
    secretAccessKey: String(process.env.AWS_SECRET_ACCESS_KEY),
  },
  region: "ap-northeast-2",
});

// 2) multerS3를 사용하여 S3에 접근하는 미들웨어를 multer를 이용하여 만들어줌
const upload = multer({
  storage: multerS3({
    s3: s3,
    bucket: "bookmore-image",
    key: (req, file, callback) => {
      callback(null, `${file.originalname}-${Date.now().toString()}`);
    },
  }),
});

// 3) 미들웨어를 사용하기 위해 next-connect를 사용함
const apiRoute = nextConnect<NextApiRequest, NextApiResponse>({
  // Handle any other HTTP method
  onNoMatch(req, res) {
    res.status(405).json({ error: `Method '${req.method}' Not Allowed` });
  },
});

// 3) 만들어준 미들웨어를 추가해줌
apiRoute.use(upload.single("profileImage"));

// 4) 회원 가입 처리
apiRoute.post(async (req, res) => {
  // 이메일, 닉네임, 프로필 이미지 경로를 받음
  const { email, nickname, profileImageUrl } = req.body;
  const profileImageLocation = (req as any).file?.location;
  const profileImage =
    profileImageUrl === undefined ? profileImageLocation : profileImageUrl;

  // 서비스에 유저 정보를 추가해줌
  const user = await prisma.user.create({
    data: {
      email,
      nickname,
      profileImage,
    },
  });

  // 성공했다는 메시지를 주고 user 정보를 리턴함
  res.status(200).json({ user });
});

export default apiRoute;

// 3) config 설정(미들웨어에서 이미지 데이터를 처리하기 위해 반드시 필요함)
export const config = {
  api: {
    bodyParser: false, // Disallow body parsing, consume as stream
  },
};

1) 버킷에 대한 정보 설정

node.js에서 aws s3를 사용하기 위해 먼저 aws-sdk를 다운로드 받아주어야 합니다. 이때 그냥 aws-sdk로 다운 받으면 v2가 다운 받아지기 때문에 나중에 에러가 발생합니다. 따라서 반드시 yarn add @aws-sdk/client-s3 로 다운 받아야 합니다.(아래 북마크 참고)

Type 'S3' is missing the following properties from type 'S3Client': destroy, middlewareStack, sendts(2739)

다운 받은 후 S3 instance를 만들고 그 값을 넣어줍니다.

2) multer 이용한 미들웨어 function 작성

multer와 multer-s3를 이용하여 이미지 파일을 s3에 넣어주는 미들웨어를 작성해줍니다. 이를 위해서 먼저 yarn add multer multer-s3 커맨드 창에 쳐서 모듈을 다운 받아준 후 위 코드와 같이 작성해주면 됩니다. 이때 bucket에 s3에서 만든 bucket 이름을 넣어주면 되고 key 값으로 저장될 파일 이름을 설정해줄 수 있습니다.

3) next-connect를 이용하여 미들웨어 추가

next-connect는 next.js에서 미들웨어를 쉽게 사용하기 위해 필요한 라이브러리입니다. 따라서 yarn add next-connect 를 입력하여 해당 모듈을 다운 받아 준 후 위와 같이 작성해줌으로써 multer를 이용하여 만든 미들웨어를 추가해줄 수 있습니다.

저는 파일 하나만 받으면 돼서 upload.single 을 사용하였는데, 여러 개의 이미지 파일을 받고 싶다면, upload.arrayupload.fields를 사용하면 됩니다. 자세한 사용법은 아래의 공식 문서를 참고해주세요

multer/README-ko.md at master · expressjs/multer

또한 미들웨어에서 이미지 파일을 사용하기 위해서 body parsing을 멈출 필요가 있습니다. 따라서 가장 아래의 config 설정에서 parsing을 false로 해주시면 됩니다.

3. 유저 정보 저장

나머지 코드는 유저 정보를 db에 넣어주는 코드입니다. 먼저 request body로 들어오는 url을 받아주고, request file로 들어오는 file 정보를 받아줍니다. 이때 typescript를 사용하기 때문에 request에 file 속성이 없다는 에러가 날 수 있습니다.

이는 file 속성이 원래 있는 속성이 아니라 미들웨어인 multer에서 추가로 넣어주는 속성이라 그렇습니다. 이를 해결하기 위해 원래 NextApiRequest 타입을 extend 해서 새로운 타입을 정의하여 사용해야 되지만, 구현할 부분이 많아서 임시로 any type을 주었습니다.

이후 prisma를 이용해서 user db에 값을 넣어주고 넣어진 결과 값을 client에게 return해주며 끝납니다.

정리

처음으로 미들웨어를 사용하고, s3를 사용하다 보니 생각보다 많이 헤맸고 에러도 많이 발생했지만 결국 구현해냈습니다. 다음으로는 accessTokenrefreshToken을 발급하는 부분을 구현해보겠습니다.

profile
프론트엔드 취업을 준비 중입니다.

0개의 댓글