AWS S3, multer를 이용해 DB에 이미지 저장하기(feat. formData)

Sujeong K·2023년 1월 19일
0


미술 작품 거래 서비스(Decouvrir) 프로젝트를 할 때, 유저가 자신의 작품을 판매하기 위해 직접 상품을 등록할 수 있는 기능이 필요했다. 그 기능의 포인트는 이미지를 첨부파일로 받아서 DB에 상품 이름, 가격 등과 함께 저장하는 로직이었는데, 이를 위해서

  1. AWS S3에 이미지를 업로드
  2. 이미지 url을 클라이언트로 일단 응답
  3. 클라이언트에서는 그 경로와 함께 다른 텍스트 정보들을 서버에 전달
  4. 서버에서 상품 DB에 정보를 저장

위와 같은 방법으로 최종 정리되었다.


여러 서칭을 통해 AWS S3 버킷 세팅을 마쳤고 multer-s3를 이용해 파일을 업로드 하기 위한 imageRouter의 코드는 아래와 같이 작성했다.

import dotenv from "dotenv";
import AWS from "aws-sdk";
import multerS3 from "multer-s3";
import multer from "multer";
import { Router } from "express";

// loads .env file contents into process.env
dotenv.config();

const imageRouter = Router();

// aws s3 설정
AWS.config.update({
  region: process.env.AWS_REGION,
  apiVersion: "latest",
  credentials: {
    accessKeyId: process.env.S3_ACCESS_KEY_ID,
    secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
  },
});

const s3 = new AWS.S3();

// multer를 이용해 이미지를 업로드하는 함수
const imageUpload = multer({
  storage: multerS3({
    //저장 공간 정보
    s3: s3,
    bucket: "decouvrir",
    key: function (req, file, cb) {
      // 확장자가 올바른지 확인
      const extention = file.mimetype.split("/")[1];
      if (!["png", "jpg", "jpeg", "gif", "bmp"].includes(extention)) {
        return cb(new Error("이미지 파일을 등록해주세요."));
      }
      // 파일 이름 지정
      cb(null, `${Date.now()}_${file.originalname}`);
    },
  }),
  acl: "public-read",
});

// post 요청이 들어왔을 때
imageRouter.post(
  "/upload",
  // 하나의 이미지 파일 업로드
  imageUpload.single("image"),
  async (req, res, next) => {
    try {
      res.json({
        // 이미지 경로를 res로 보내줌
        message: "이미지 저장 성공",
        imagePath: req.file.location,
      });
    } catch (err) {
      next(err);
    }
  }
);

export { imageRouter, imageUpload };

이렇게 작성을 하고 postman을 통한 테스트는 완료되었는데, 프론트에서 API 요청을 보내면 계속 오류가 나는 것이다ㅠㅠ 함참동안 뭐가 잘못되었을까 고민했는데, 결국 문제점을 찾아냈다.

function addProduct(e) {
  e.preventDefault();
  const productName = productNameInput.value;
  const content = contentInput.value;
  const price = priceInput.value;
  const category = categoryInput.value;

  const formData = new FormData();
  formData.append("image", photoFile.files[0]); // 파일 첨부

  fetch(`/api/images/upload`, {
    method: "POST",
    body: JSON.stringify(formData), 
  })
    .then((response) => response.json())
    .then((data) => {
      image = data.imagePath;
      const newProductData = {
        painterEmail,
        painterName,
        productName,
        price,
        content,
        category,
        categoryId,
        image,
      };

      Api.post(`/api/product`, newProductData);
      alert("상품 등록이 완료되었습니다!");
    });

}

postBtn.addEventListener("click", addProduct);

imageRouter로 POST 요청을 보낼 때 body를 그냥 formData로 했어야 했는데 JSON.stringify(formData)로 보내고 있었던 것이 원인이었다😭 내가 생성해준 formData 객체를 그냥 보내줬으면 됐는데 그걸 stringify 했으니 제대로 업로드되지 않았던 것.. 형식이 다른 데이터를 어떻게 보내야 하는지에 대한 고민이 부족했던 것이 느껴져서 많이 반성했다. (이렇게 간단한 걸 며칠동안 붙잡고 있었다니..)


더 알아보니 요청 헤더에 담기는 정보인 Content-type 중 multipart 자체가 폼에 이미지와 같은 파일과 제목, 설명과 같은 텍스트처럼 서로 다른 형식의 데이터가 함께 서버로 전달되어야 할 때 사용할 수 있는 타입이기 때문에 내가 했던 것 처럼 api 요청을 두 번 보낼 필요 없이 formData에 모든 정보를 담아서 한 번에 전달할 수 있다고 한다. 일단 S3에 이미지를 올리는 건 생각하지 말고, formData를 활용하는 방법을 더 익혀야겠다고 생각했다. (이미지가 다뤄지지 않는 웹서비스는 드물기 때문에 더더욱!💪)

참고 자료

profile
차근차근 천천히

0개의 댓글