서론
오늘은 NestJS에서 파일을 업로드 받고, AWS S3에까지 저장하는 REST API를 공유해보려합니다.
aws-sdk v2로 된 한국어 자료들은 많이 있는데, version3을 사용해서 만들어봤습니다.
일단 필자도 처음에는 aws-sdk v2를 사용해서, 파일 업로드 기능을 만들었습니다.
그런데, 서버를 run 할 때 version3으로 migrate해달라는 굉장히 거슬리는 문구가 뜨는 걸 볼 수 있었습니다.
(node:2109) NOTE: We are formalizing our plans to enter AWS SDK for JavaScript (v2) into maintenance mode in 2023. Please migrate your code to use AWS SDK for JavaScript (v3). For more information, check the migration guide at https://a.co/7PzMCcy (Use `node --trace-warnings ...` to show where the warning was created) |
* 참고 : aws-sdk v2 : aws-sdk - npm (npmjs.com)
그렇기에 v3의 장점을 찾아보게 되었고, 후술할 장점들에 이끌려 version 3을 지원하는 패키지로 마이그레이션 해주고자 합니다.
* 참고 aws-sdk v3 : @aws-sdk/client-s3 - npm (npmjs.com)
aws-sdk v3의 장점
그럼 과연 왜? Why? v3로 마이그레이션 해주어야하는가? 어떤 장점이 있는지 조사해보았습니다.
* 출처 : AWS SDK v2 or v3 - which one should you use? - DEV Community
위 글에서는 크게 3가지, Modular architecture, Middleware, Individual Configuration를 장점으로 꼽고있 습니다.
무엇보다 눈에 띄는건 ' Modular architecture'이다. 기존에 v2에서는 내가 S3 관련 매서드만 쓰고 싶더라도, aws-sdk를 통채로 import 했어야했습니다. 하지만 지금은 원하는 모듈만 import해서 사용할 수 있다는 점입니다.
// v2
import * as AWS from 'aws-sdk';
import AWS from 'aws-sdk/global';
// v3
import { ListBucketsCommand, S3Client } from "@aws-sdk/client-s3";
이외에도 구조 개선으로 가독성, 디버깅 경험 확대 등 여러 장점이 있습니다.
NestJS에서 적용
// file.controller.ts
import { Controller, Post, UploadedFile, UseInterceptors } from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { ApiConsumes, ApiTags } from '@nestjs/swagger';
import { ApiFile } from './decorators';
import { FileService } from './providers/file.service';
import { UploadFile } from '#entities/file';
@ApiTags('file')
@Controller('file')
export class FileController {
constructor(private readonly fileService: FileService) {}
@ApiConsumes('multipart/form-data')
@ApiFile()
@Post('upload')
@UseInterceptors(FileInterceptor('file'))
async uploadFile(@UploadedFile() file: Express.Multer.File): Promise<UploadFile> {
return await this.fileService.uploadSingleFile(file);
}
}
이 부분에 관해서는 NestJS 공식문서를 포함한 다른 블로그에서 잘 설명해준글이 많으니 다른 글을 참고하길 바랍니다.
* 참고 : File upload | NestJS - A progressive Node.js framework
// file.service.ts
@Injectable()
export class FileService {
...
public async uploadSingleFile(file: Express.Multer.File): Promise<UploadFile> {
if (!file) throw new BadRequestException('File is not Exist');
const awsRegion = this.config.get('aws.region');
const bucketName = this.config.get('aws.s3.bucketName');
const client = new S3Client({
region: awsRegion,
credentials: {
accessKeyId: this.config.get('aws.accessKey'),
secretAccessKey: this.config.get('aws.secretKey'),
},
});
const key = `${Date.now().toString()}-${file.originalname}`;
const params = {
Key: key,
Body: file.buffer,
Bucket: bucketName,
ACL: ObjectCannedACL.public_read,
};
const command = new PutObjectCommand(params);
const uploadFileS3 = await client.send(command);
if (uploadFileS3.$metadata.httpStatusCode !== 200)
throw new BadRequestException('Failed upload File');
...
}
}
이때 S3는 파일을 key-value 형태로 저장하기에 key는 파일명이 됩니다. 중복을 막기 위해서 Date.now를 사용하였으며, controller에서 받아온 파일의 buffer를 Body에 담아줍니다.
ACL의 경우 Access Control List의 약자로 접근의 허용 유무를 보여줍니다. 본인은 global로 공개해도 상관없는 정책을 사용할 것이기에 public_read를 사용하였지만, 올리는 파일에 따라서 policy를 명확히 정해야할 것입니다.
S3와 IAM 계정을 생성하는 방법에 대해서도 많은 블로그에서 다루고 있기에 따로 첨부하진 않겠지만, policy 설정을 aws에서 해줘야 SDK에서 에러를 뱉지 않습니다. 코드가 똑같은데 에러를 뱉는다면 AWS 내의 권한 설정을 의심해보면 좋을 것 같습니다!
* 참고 : Amazon S3 examples using SDK for JavaScript (v3) - AWS SDK for JavaScript