1. nest cli로 컨트롤러,서비스 생성하기
- 파일 업로드 서비스의 이름은 upload 로 생성한다.
nest g mo uploads
nest g co uploads
nest g s uploads
2. 업로드 컨트롤러 구현
Nest는 파일 업로드 처리를 위해 Express의 multer 미들웨어를 제공한다.
multer는 POST 메소드로 multipart/form-data 컨텐츠 타입을 지원한다.
업로드 컨트롤러를 구현하기 앞서 Multer 라이브러리를 설치한다.
$ yarn add --dev @types/multer
이번 예제는 단일 파일 업로드를 처리한다.
@Controller('uploads')
export class UploadsController {
@Post()
@UseInterceptors(FileInterceptor('file'))
uploadFile(@UploadedFile() file: Express.Multer.File) {
console.log(file);
}
}
업로드 컨트롤러에서 단일 파일 입력을 받도록 설정한 후 포스트맨으로 API를 테스트한다.
포스트맨으로 테스트 한 결과 console.log(file) 에 다음과 같은 정보가 출력된다.
{
fieldname: 'file',
originalname: 'test.png',
encoding: '7bit',
mimetype: 'image/png',
buffer: <Buffer 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 00 00 01 46 00 00 01 3e 08 06 00 00 00 f1 a6 b9 5c 00 00 0c 6d 69 43 43 50 49 43 43 20 50 72 6f 66 69 ... 36041 more bytes>,
size: 36091
}
이제 컨트롤러의 file을 서비스로 전달하여 S3로 업로드하는 작업을 구현한다.
2. 업로드 서비스 구현
먼저 업로드 컨트롤러에서 업로드 서비스의 uploadFile 함수를 호출하도록 코드를 수정한다.
@Controller('uploads')
export class UploadsController {
constructor(private readonly uploadService: UploadsService) {}
@Post()
@UseInterceptors(FileInterceptor('file'))
uploadFile(@UploadedFile() file: Express.Multer.File) {
return this.uploadService.uploadFile(file);
}
}
업로드 서비스에서는 AWS S3로 파일을 업로드하기 위해 aws-sdk 라이브러리를 사용한다.
$ yarn add --dev aws-sdk
업로드 서비스에서 aws-sdk를 사용하기 위해 클래스에 S3 멤버 변수를 추가한다.
(실행 환경에 S3 업로드 권한을 가진 Access Key가 설정되어 있어야 한다.)
import { Injectable } from '@nestjs/common';
import * as AWS from 'aws-sdk';
@Injectable()
export class UploadsService {
s3 = new AWS.S3();
}
실제 파일 업로드를 수행하는 uploadFile 함수를 구현한다.
async uploadFile(file: Express.Multer.File) {
const AWS_S3_BUCKET = 'nestjs-upload-test-bucket';
const params = {
Bucket: AWS_S3_BUCKET,
Key: String(file.originalname),
Body: file.buffer,
};
try {
const response = await this.s3.upload(params).promise();
return response;
} catch (e) {
throw new Error('Failed to upload file.');
}
}
업로드가 성공했다면 AWS.S3.ManagedUpload.SendData 타입의 결괏값이 반환된다.
export interface SendData {
/**
* URL of the uploaded object.
*/
Location: string;
/**
* ETag of the uploaded object.
*/
ETag: string;
/**
* Bucket to which the object was uploaded.
*/
Bucket: string;
/**
* Key to which the object was uploaded.
*/
Key: string;
}
응답 객체의 Location은 업로드 된 객체의 URL을 포함한다.
{
"ETag": "\"fff730c0a3f344034854117aaf88d9ac\"",
"Location": "https://nestjs-upload-test-bucket.s3.ap-northeast-2.amazonaws.com/test.png",
"key": "test.png",
"Key": "test.png",
"Bucket": "nestjs-upload-test-bucket"
}
업로드 시 별도의 ACL을 설정하지 않았기 때문에 해당 URL로 접속할 경우 AccessDenied 가 발생한다.
<Error>
<Code>AccessDenied</Code>
<Message>Access Denied</Message>
<RequestId>BJNMX4V6S5NHCN9Q</RequestId>
<HostId>
gW79WxeQfreLOY+bmPKSxrRBaOUJ/girPZsMev1icrU5foITkTwdF8njH6VnkKJVZRjibxm0P/I=
</HostId>
</Error>
외부에서 접속 가능한 ACL로 파일을 업로드하려면 params 변수에 public-read로 ACL을 설정해야 한다.
이 경우 해당 S3 버킷의 ACL도 퍼블릭 액세스가 허용된 상태여야 한다.
S3 버킷의 퍼블릭 액세스를 허용하는 방법은 다음 문서를 참고한다.
const params = {
Bucket: AWS_S3_BUCKET,
Key: String(file.originalname),
Body: file.buffer,
ACL: 'public-read',
};