nest.js에서 S3로 이미지 여러장 업로드

jegw·2023년 9월 1일
0

TIL

목록 보기
70/77
post-custom-banner

s3에 이미지 파일을 업로드하고 반환받은 데이터에서 URL을 DB에 저장할 것이다.

코드 흐름

  1. 사용자가 이미지를 업로드한다.
  2. 프론트에서 이미지를 formData에 담는다.
  3. 서버에서 이미지를 s3에 업로드한다.
  4. 업로드 후 반환 데이터 안에서 URL에 접근해 DB에 저장한다.

프론트

imageUpload.addEventListener('change', async (e) => {
  previewImages();

  const files = e.target.files;
  for (let i = 0; i < files.length; i++) {
    const file = files[i];
    // 이미지 파일 사이즈 검사
    if (file.size > 1 * 1024 * 1024) {
      alert('파일용량은 최대 1mb입니다.');
      return;
    }
    // 확장자 검사
    if (!file.type.includes('jpeg') && !file.type.includes('png')) {
      alert('jpeg 또는 png 파일만 업로드 가능합니다!');
      return;
    }
    hasNoImage = false;
    formData.append('images', file);
  }
});
<input class="file-drop-input" multiple type="file" id="imageUpload" />

type이 file인 input에 'change'이벤트를 추가한다. formData에 저장할 때 키로 사용된 'images'로 서버의 S3에서 업로드 된 이미지를 찾을 것이다.

미들웨어

모듈을 사용해도 된다. 하지만 우리는 미들웨어로 사용하는 법을 알아볼 것이다.

upload.middleware.ts

// s3
import { HttpStatus, Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import * as multer from 'multer';
import * as multerS3 from 'multer-s3';
import * as AWS from 'aws-sdk';
import * as path from 'path';
import { v4 as uuid } from 'uuid';
@Injectable()
export class UploadMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    try {
      const s3 = new AWS.S3({
        accessKeyId: process.env.S3_ACCESS_KEY,
        secretAccessKey: process.env.S3_ACCESS_KEY_SECRET,
        region: process.env.AWS_REGION,
      });
      const allowedExtensions = ['.png', '.jpg', '.jpeg', '.jfif', '.exif', '.tiff', '.bmp', '.gif'];
      const upload = multer({
        storage: multerS3({
          s3,
          bucket: process.env.BUCKET_NAME,
          contentType: multerS3.AUTO_CONTENT_TYPE,
          shouldTransform: true,
          key: function (_, file: Express.Multer.File, callback) {
            const fileId = uuid();
            const type = file.mimetype.split('/')[1];
            if (!allowedExtensions.includes(path.extname(file.originalname.toLowerCase())) || !file.mimetype.startsWith('image/')) {
              const errorMessage = '이미지 파일만 업로드가 가능합니다.';
              const errorResponse = { errorMessage };
              return res.status(HttpStatus.BAD_REQUEST).json({ errorResponse });
            }
            const fileName = `${fileId}.${type}`;
            callback(null, fileName);
          },
          acl: 'public-read-write',
          limit: { fileSize: 5 * 1024 * 1024 },
        }),
      });
      
      upload.array('images', 5)(req, res, next); // 이미지를 업로드
      
    } catch (error) {
      console.error(error);
      res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ errorMessage: '파일 업로드 에러' });
    }
  }
}
upload.array('images', 5)(req, res, next);

이 한줄이 이미지를 업로드하는 부분이다.이미지를 찾아서 s3에 업로드하고 결과를 req.files에 담고 다음 미들웨어로 넘어가는 코드다. 다른 부분은 s3설정을 하고 파일의 조건을 검사하는것이다.
숫자 5는 5개의 이미지만 업로드하고 나머지는 무시한다는 의미이며, 하나의 이미지만 업로드할 때는 upload.single을 사용할 수 있다.

컨트롤러

upload.array는 reqest객체의 files 배열에 결과를 담아서 다음 미들웨어로 넘겼다.

@Post()
  async create(@Req() req: IRequest){
    const _files = req.files;
    const { id } = await this.productService.create(_files);
    return { id };
  }

컨트롤러에서는 req.files에서 처리된 이미지파일들에 접근할 수 있다.
files[0].location 으로 접근하면 s3에 업로드된 이미지의 URL에 접근할 수 있다.
이것을 DB에 저장하는 로직을 짜면 된다.

post-custom-banner

0개의 댓글