아마존에서 제공하는 Simple Storage Service
확장성, 데이터 가용성 및 보안과 성능을 제공하는 객체 저장소 서비스
NestJS 프레임워크 환경에서 Swagger를 통해 S3 Bucket에 이미지를 업로드
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { UploadModule } from './upload/upload.module';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
envFilePath: '.env.dev',
ignoreEnvFile: process.env.NODE_ENV === 'prod',
}),
UploadModule,
],
controllers: [],
providers: [],
})
export class AppModule {}
먼저 AWS 업로드 권한을 위해 환경 변수를 설정해야 하므로 app 모듈에 ConfigModule.forRoot()를 적용한다.
isGlobal : 다른 모듈에서 특별히 import하지 않아도 사용 가능하게 전역 모듈로 설정envFilePath : 환경 변수 설정 파일 경로ignoreEnvFile : 환경 변수 파일(.env.dev) 로딩을 비활성화할 때 설정AWS의 Access Key ID와 Secret access key 값은 권한 문제로 절대 유출되면 안되므로 공개적인 클래스에 선언하지 않는다. 때문에 외부 파일에서 (환경 변수 파일 .env.dev) 가져와 사용할 수 있도록 Config를 설정한다. 권한 key 값이 포함된 환경 변수 파일은 특히 gitHub같은 오픈 소스 사이트에 업로드하지 않도록 주의한다.
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { UploadController } from './upload.controller';
import { UploadService } from './upload.service';
@Module({
imports: [ConfigModule],
providers: [UploadService],
controllers: [UploadController],
})
export class UploadModule {}
ConfigModule의 isGlobal이 true이므로 imports: [ConfigModule]을 굳이 설정해 줄 필요 없음
import {
Controller,
Post,
UploadedFile,
UseInterceptors,
} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { ApiBody, ApiConsumes, ApiProperty } from '@nestjs/swagger';
import { UploadService } from './upload.service';
export class FileUploadDto {
@ApiProperty({ type: 'file' })
file: any;
}
@ApiBody에 지정할 타입에 @ApiProperty 속성을 부여해 file 지정
@Controller('upload')
export class UploadController {
constructor(private readonly uploadService: UploadService) {}
@Post('file')
@UseInterceptors(FileInterceptor('file'))
@ApiConsumes('multipart/form-data')
@ApiBody({
description: 'image upload',
type: FileUploadDto,
})
uploadFile(@UploadedFile() file) {
console.log(file);
return this.uploadService.uploadFile(file);
}
}
@ApiConsumes : swagger에서 input file 추가@ApiBody : swagger에서 file을 받아올 type(DTO) 지정@UseInterceptors : input한 file을 인터셉트 (UploadedFile에 넘겨주는?)@UploadedFile : 업로드한 파일을 객체로 받아옴 (type: object)
console.log(file)을 통해 넘어온 file의 buffer값과 이외 정보들을 확인할 수 있다.
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import * as AWS from 'aws-sdk';
@Injectable()
export class UploadService {
constructor(private readonly configService: ConfigService) {}
ACCESS_KEY = this.configService.get('ACCESS_KEY_ID');
SECRET_KEY = this.configService.get('SEC_ACCESS_KEY');
BUCKET_NAME = this.configService.get('BUCKET_NAME');
npm i aws-sdk 라이브러리 설치 후 import * as AWS from 'aws-sdk'
환경 변수 파일에서 AWS 권한 key 값을 끌어와 선언
s3 = new AWS.S3({
accessKeyId: this.ACCESS_KEY,
secretAccessKey: this.SECRET_KEY,
});
사용할 스토리지의 accessKeyId와 secretAccessKey로 권한 인증
this.s3.createBucket( { Bucket: this.BUCKET_NAME, CreateBucketConfiguration: { LocationConstraint: 'ap-northeast-2', }, }, function (err, data) { if (err) { console.log(err); } else { console.log('Bucket Created Successfully.', data); } }, );S3에 저장하기 위해선 Bucket이 필요한데 보통 이미 업무에 존재하는 Bucket을 사용하고 직접 Bucket을 Create 해야 하는 상황은 드물다. (하지만 예제 업로드를 위해선 Create 해야 한다.)
→ Bucket name을 설정하고 생성할 region을 설정
public async uploadFile(file) {
// timestamp for image unique name
const date = new Date().getTime();
console.log(date);
const param = {
Bucket: this.BUCKET_NAME,
Key: `image/${date + file.originalname}`,
Body: file.buffer,
ACL: 'public-read', // bucket에 조회 public 권한
ContentType: file.mimetype,
};
try {
const stored = await this.s3.upload(param).promise();
console.log('Image Saved Successfully.');
return stored;
} catch (err) {
console.log(err);
}
}
}
Date().getTime()을 이용해서 S3에 업로드 할 이미지에 고유한 이름 지정file.buffer 사용public-read 선언file.mimetype 사용이후 s3.upload(param)으로 파일을 업로드한다. 이 때, 비동기 처리를 위해 .promise()를 사용한다. (AWS에서 promise 지원)
const stored = () => { return new Promise((resolve, reject) => { this.s3.upload(param, (err, data) => { if (err) { console.log('rejected'); reject(err); } else { console.log('resolved'); resolve(data); } }); }); }; return await stored();
.promise()를 사용하지 않고 원시적으로 비동기 처리하는 방법
이 방법을 사용하진 않으나 에러 핸들링 과정을 이해하기 위해 시도해본다.
💡 비동기 함수는 Promise를 return하도록 작성해야 한다.
swagger에서 file을 넘겨주면 위와 같이 S3에 저장된다. ResponseBody엔 s3.upload.promise() 객체를 리턴해 저장된 파일의 URL Location과 저장된 파일의 이름 key값이 조회된다.