[Node.js] Multer-S3를 이용한 이미지 업로드

Yalstrax·2021년 9월 23일
19

Back End

목록 보기
1/22
post-thumbnail

Why Multer-S3?

Front-end에서 유저가 로컬에서 업로드한 이미지를 저장하는 방법은 여러가지가 있습니다.

Server에 이미지를 저장하는 폴더를 만들어 클라이언트로부터 요청받은 파일을 저장하는 것이 있고,

이미지를 따로 저장하는 서버를 만들어 저장할 수 있습니다.

또한, DB에 Blob 타입으로 저장할 수 있습니다.

위의 경우, 사용하는 서버와 DB의 상태 또는 이를 구동하는 PC의 성능에 따라 퍼포먼스가 달라질 수 있습니다.

그래서, AWS S3 버킷에 이미지 파일을 저장하고, DB엔 그 버킷의 이미지 파일 경로(이미지 주소)를 저장하고, 서버는 이 경로를 클라이언트로 응답합니다.

이렇게 하게 되면 PC의 성능을 고려하지 않아도 되며, 사용한 만큼 비용을 지불하는 것으로 보다 쾌적하게 백엔드를 구성할 수 있습니다.

이를 Multer-S3 와, AWS-SDK 모듈을 사용하여 구현할 수 있습니다.

구현

설치

npm install multer multer-s3 aws-sdk --save

다음과 같이 설치합니다.

Front-End

백엔드를 구성하기 전에, 클라이언트는 아래와 같이 구현했습니다.

src/image.js

...

function image() {
  const requestImg = async (event) => {
    // form tag를 사용하지 않아도 formdata를 만들 수 있습니다.
    let formData = new FormData();
    formData.append('image', event.target.files[0]);
    // 생성한 폼 데이터에 파일 객체를 할당하고, 서버에 요청을 보냅니다.
    try {
      const imageRes = await axios.post(`localhost:4000/image`, formData);
    } catch (error) {
      console.log(error);
      alert('server error');
    }
  };
  return (
    <Wrap>
      <div id='imageEdit'>
        <input
          type='file'
          id='image_uploads'
          name='image'
          accept='image/*'
          onChange={requestImg}
        ></input>
      </div>
    </Wrap>
  );
}


...

input 태그의 typefile이 되어야하며, 이미지 타입의 파일만 선택하기 위해 accept 속성을 추가했습니다. 업로드 되면, onChange 이벤트가 발생하여 requestImg 함수가 실행되어 서버로 axios요청이 가게 됩니다.

Back-End

백엔드에선 클라이언트로부터 요청받은 파일을 처리해야합니다. 그 전에, Route 설정 및 AWS-SDK를 설정해야합니다.

config/s3.json

{
  "accessKeyId": "YOUR AWS ACCESS KEY",
  "secretAccessKey": "YOUR AWS ACCESS SECRET KEY",
  "region": "ap-northeast-2"
}

json파일의 첫번째와 두번째는 AWS에서 할당받은 액세스 키와 액세스 암호 키 입니다. 세번째는 S3 버킷을 생성한 지역입니다. 보통 서울 리전에서 생성하므로, ap-northeast-2가 되겠습니다.

이 액세스 키는 AWS 콘솔의 IAM 메뉴에서 생성할 수 있습니다.

IAM 메뉴로 이동하고, 사용자 탭에서 사용자 추가를 선택합니다.

이름은 자유롭게 짓고, 자격 증명 유형은 액세스 키를 선택하고 다음을 누릅니다.

다음으로, 기존 정책 직접 연결을 선택하고, AmazonS3FullAccess를 선택하고 다음을 누릅니다.

세번째인 키, 값 태그 설정은 필요하지 않다면 할 필요 없이 다음으로 이동합니다.

다음을 누르고, 이제 사용자 만들기를 클릭하여 사용자를 생성합니다.

이제 생성하면 액세스 키 ID와 액세스 암호 키를 부여받습니다. 이를 개인적으로 메모해두고, 외부엔 절대로 노출되어선 안됩니다.

코드 상으론 환경변수로 관리하거나, gitignore로 블락처리 해야합니다. 공개될 경우, 폭탄 과금의 우려가 있다고 하네요...

이렇게 만들어진 키를 위 s3.json에 입력합니다.

modules/multer.js

저는 modules라는 폴더를 새로 만들고, 위와 같은 이름의 파일을 만들었습니다.

이 파일은 IAM 액세스 키와 비밀 키를 가지고 유저의 S3 버킷에 접근하고, 업로드 하기 위한 로직을 가진 파일입니다.

const multer = require('multer');
const multerS3 = require('multer-s3');
const aws = require('aws-sdk');
aws.config.loadFromPath(__dirname + '/../config/s3.json');

const s3 = new aws.S3();
const upload = multer({
  storage: multerS3({
    s3: s3,
    bucket: 'YOUR BUCKET NAME',
    acl: 'public-read',
    contentType: multerS3.AUTO_CONTENT_TYPE,
    key: function (req, file, cb) {
      cb(null, `${Date.now()}_${file.originalname}`);
    },
  }),
});
module.exports = upload;

이 파일의 upload라는 함수가 실행되면, 설정한 이름의 Bucket에 파일을 업로드할 수 있습니다.

key 속성은 업로드하는 파일이 어떤 이름으로 버킷에 저장되는가에 대한 속성입니다.

위 속성대로라면, 버킷에 업로드되는 파일(객체)의 이름은 현재 시각_유저가 업로드한 파일의 이름.이미지 확장자가 됩니다.

구현이 모두 완료되면, 위와 같이 버킷에 담기는 것을 확인할 수 있습니다.

routes/image.js

이제 routes로 분기하고, multer.js에 작성한 upload함수를 routes에 담아야 합니다.

const express = require('express');
const router = express.Router();
const upload = require('../modules/multer');
const { controller } = require('../controllers');

router.post('/image', upload.single('image'), controller.image.post);

module.exports = router;

multer.js에서 export하는 upload를 가져오고, 이미지 저장을 처리하는 routes의 두번째 인자로 전달합니다.

여기서 두번째 인자인 upload.single()의 파라미터로는 Front-End에서 Formdataappend 할 때의 키 이름으로 설정해야합니다.

controllers/image.js

이제 Front-End로부터 요청받은 파일을 S3 버킷에 저장하고, DB에 이 버킷의 파일 경로를 저장합니다. 그리고 응답으로 이 경로를 클라이언트로 응답하면 끝입니다!

const { img } = require('../../models');

module.exports = {
  post: async (req, res) => {
    ...
    img.imageURL = req.file.location;
    await img.save();
    res.status(200).json({ img: req.file.location });
  },
};

클라이언트에서 이미지를 업로드하고, 서버로 페이로드에 폼데이터를 담아 보내면, 유저의 AWS S3 버킷에 업로드한 파일(객체)를 저장합니다. 이렇게 저장된 이미지의 메타데이터를 req.file로 받을 수 있습니다.

req.file은 객체이며, 이 객체의 속성 중, S3 버킷에 저장된 이미지 파일의 경로(이미지 주소)가 req.files.location이라는 키로 전달됩니다.

이 경로를 DB에 저장하고, 서버는 이 이미지 주소를 응답하면, Front-End에서 이 이미지를 핸들링할 수 있게 됩니다.

수많은 삽질...🔧

프로젝트를 진행하면서 느꼈던 다양한 어려움 중에 가장 막막하게 다가왔던 어려움들이 있습니다.

사용할 땐 대수롭지 않게 너무 당연하게 사용했던 기능들이 직접 구현하려니 너무 어려웠다는 것이었습니다...

그 중에 하나가 바로 이미지를 업로드하고, 이를 어떻게 핸들링할까... 서버에 저장할까? DB에 저장할까? 너무 많은 고민이었고, 어려움이었습니다.

수많은 구글링과 삽질 끝에, multer-s3aws-sdk를 적극 사용하기로 했고, 성공적으로 구현하여 굉장히 뿌듯했습니다!

제 글이 이 기능을 구현하기 위해 구글링하는 다른 분들에게 많은 도움이 되길 바라며 이 글을 썼습니다. 긴 글 읽어주셔셔 감사드립니다! 😆

profile
즐겁다면 그것만으로 만만세!

3개의 댓글

comment-user-thumbnail
2021년 9월 24일

preSignUrl을 사용해보세요~

1개의 답글
comment-user-thumbnail
2022년 9월 26일

감사합니다! 혹시 이미지 파일을 여러 개 올릴때는 upload.single을 upload.array로만 바꿔주면 되는 건가요?!

답글 달기