[Node.js] AWS S3 로 이미지 업로더 구현하기

Yeongju Yun·2022년 6월 17일
1
post-thumbnail

구현 목적

프로젝트 개발 초기 naver Book API를 이용해서 일괄적으로 책 정보를 받아와서 DB에 저장했다. 그러다보니 이미지 url을 text로 입력 받는 형태로 기존 상품 수정 페이지를 구현하게 되었는데 관리자가 새로운 책을 등록할 경우 이미지 파일을 업로드 하여 DB에 저장해야하기 때문에 이를 해결하기 위해 알아본 방법 중 하나가 Amazon Simple Storage Service(Amazon S3) 를 활용하는 것이다.

Amazon Simple Storage Service(Amazon S3)

Simple Storage Service의 약자로 파일 서버의 역할을 하는 서비스다. 일반적인 파일서버는 트래픽이 증가함에 따라서 장비를 증설하는 작업을 해야 하는데 S3는 이와 같은 것을 대행한다.

AWS S3 bucket 을 생성해서 해당 버킷에 이미지를 업로드하게 되면 버킷에 저장된 이미지의 url 정보를 받아올 수 있다. 상품이 추가, 수정, 삭제될 때 버킷의 url 정보도 같이 수정될 수 있도록 구현해보자. AWS S3를 이용하기 위해서 IAM 접근 권한 설정, S3 버킷 생성하는 과정이 필요하나 본 포스팅에서는 다루지 않겠다.

multer, multer-s3, aws-sdk

이미지 업로더 기능을 구현하기 위해서 multer와 multer-s3, aws-sdk 를 다운로드 한다.

Multer는 파일 업로드를 위해 사용되는 multipart/form-data 를 다루기 위한 NodeJS의 미들웨어이다. Multer는 단일 및 다중 파일 업로드 모두를 지원해주기 때문에 node에서 파일 업로드 기능을 구현할 때 많이 사용되고 있다.

aws-sdk는 Amazon S3 버킷에서 정보를 가져오고 이 버킷에 파일을 업로드하기 위해 사용된다.


구현 방법

AWS에서 발급한 엑세스 키와 시크릿 키 등은 민감한 정보이므로 .env 파일에 저장해두고 공개되지 않게 관리해야 한다. 구현하는 과정에서 TypeError: this.client.send is not a function 라는 에러가 나서 한참을 찾아봤는데 Multer S3와 AWS SDK 버전이 호환되지 않아서 생겼던 문제였다. multer를 2.x 버전으로 Downgrade 하여 에러를 해결했다.

.env

AWS_BUCKET_NAME= // 버킷 이름 입력
AWS_BUCKET_REGION= // 사용자 사용 지역 (서울의 경우 ap-northeast-2)
AWS_ACCESS_KEY= //액세스 키 입력
AWS_SECRET_KEY= // 비밀 액세스 키 입력

src/util/s3.js

import multer from "multer";
import multerS3, { AUTO_CONTENT_TYPE } from "multer-s3";
import { S3 } from "aws-sdk";

const bucketName = process.env.AWS_BUCKET_NAME;
const region = process.env.AWS_BUCKET_REGION;
const accessKeyId = process.env.AWS_ACCESS_KEY;
const secretAccessKey = process.env.AWS_SECRET_KEY;

const s3 = new S3({
  accessKeyId: accessKeyId, // 액세스 키 입력
  secretAccessKey: secretAccessKey, // 비밀 액세스 키 입력
  region: region, // 사용자 사용 지역 (서울의 경우 ap-northeast-2)
});

const upload = multer({
  storage: multerS3({
    s3: s3,
    bucket: bucketName, // 버킷 이름 입력
    contentType: AUTO_CONTENT_TYPE,
    key: (req, file, cb) => {
      cb(null, `images/${Date.now()}_${file.fieldname}`);
    },
  }),
});

export { upload, s3 };

src/routers/book-router.js

import { upload } from "../utils/s3";

// 책 추가
bookRouter.post("/book", adminRequired, async (req, res, next) => {
  try {
    const { title, category, author, description, image, price, publisher } =
      req.body;
    const bookInfo = {
      title,
      category,
      author,
      description,
      image,
      price,
      publisher,
    };
    const addedBook = await bookService.addBook(bookInfo);
    res.status(201).json(addedBook); // json 형태로 보내줌
  } catch (err) {
    next(err);
  }
});

// 책 이미지 추가
bookRouter.post(
  "/bookImage",
  adminRequired,
  upload.single("image"),
  async (req, res, next) => {
    res.json(req.file.location);
  }
);

처음에는 이미지 파일까지 한번에 post 요청을 보냈는데 이미지 데이터에 빈 객체가 들어와서 처리하기가 힘들었다. 구글링으로 찾아보니 폼에서 업로드한 파일을 서버에 전송하기 위해서는 FormData를 사용해야 한다는 글을 보고 코드를 위와 같이 수정했다.

프론트 단에서 이미지 저장을 위해 formData를 받는 formPost API를 따로 만들고 이미지 API Post 요청을 먼저 한 후에 해당 결과를 변수에 담아 저장된 이미지 경로를 기존 데이터와 함께 Post 요청을 보냈다. 서버 단에서는 image를 처리하는 라우터를 만들어서 s3 buckcet에 저장될 수 있도록 했다. 이렇게 하니 업로드 한 이미지가 버킷에 잘 저장이 되었다.

// 책 이미지 삭제
bookRouter.delete("/bookImage", adminRequired, async (req, res, next) => {
  try {
    const { imgUrl } = req.body;
    const bucketName = process.env.AWS_BUCKET_NAME;

    s3.deleteObject(
      {
        Bucket: bucketName,
        Key: `images/${imgUrl}`,
      },
      (err, data) => {
        console.error(err);
        console.log(data);
      }
    );
    res.json("기존 이미지가 삭제되었습니다.");
  } catch (err) {
    next(err);
  }
});

상품을 삭제할 경우와 기존 이미지를 수정할 경우도 구현해보았다. 이미지 수정의 경우에는 기존 이미지를 삭제하고 새로운 이미지를 다시 저장하는 방식으로 처리했고 이를 위해 s3.deleteObject 를 활용해서 이미지를 삭제하는 라우터를 만들었다.

이미지 로더 구현

  // image loader
  imageInput.addEventListener("change", imageloader);
  function imageloader() {
    const file = imageInput.files[0];
    if (file) {
      currentImageInput.src = URL.createObjectURL(file);
      currentImage = currentBook.image;
    }
  }

이미지 수정 시, 기존 이미지와 업로드한 이미지 확인을 위해 이미지 로더를 구현했다. currentImageInput 라는 이미지태그를 만들고 src 에 기존 이미지 소스 설정하고 change 이벤트 핸들러를 이용해 이미지가 바뀌면 해당 이미지를 보여줬다. 기존 이미지는 currentImage 라는 변수에 저장해서 이미지를 삭제할 때 활용했다.

구현하면서 느낀점

S3를 활용하기 위해 AWS 를 처음 이용해보았는데 이리 저리 많이 헤멨다. 구현은 간단하다고 들어서 금방 끝날 줄 알았는데 AWS 가입하고 이것 저것 설정하는 과정에서도 관련 블로그를 참고해서 따라했는데 뭔가 꼬였는지 버킷에 이미지가 저장이 안되서 기존 버킷이랑 IAM 삭제하고 다시 만들었더니 다행히 잘 작동했다. 무엇이든지 처음은 익숙하지 않고 낯설어서 더 어렵게 느껴지는 거 같다. 에러에 익숙해지고 AWS 도 마찬가지로 자주 접하고 이용하다보면 자연스럽게 핸들링할 수 있을 것이다. 부족한 포스팅이지만 S3를 이용하시는 분들 께 도움이 되기를 바란다.


[참고링크]

S3
AWS S3 관련 블로그
AWS-SDK
FormData

0개의 댓글