일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- MySQL
- mongoose
- 자료구조
- MongoDB
- Dinosaur
- Sequelize
- cookie
- game
- jest
- TypeScript
- dfs
- 게임
- Express
- typeORM
- nestjs
- AWS
- nodejs
- Bull
- 정렬
- Queue
- OCR
- react
- Python
- JavaScript
- flask
- class
- 공룡게임
- Nest.js
- GIT
- Today
- Total
포시코딩
[Nest.js] 원하는 시점에 AWS S3에 파일 저장 본문
개요
https://devkkiri.com/post/96bdd7e2-3328-4450-8e54-332cd90d4066
위 블로그를 참고하여 기존의 서버 내부에 저장되던 이미지 저장 기능을
S3에 저장되게 변경하긴 했으나
service에 들어오기도 전에 S3에 이미지가 저장되고 있었고
만약 게시글 저장이 실패한다면 저장되지 말아야 하는 파일이 그대로 AWS S3에 남아있는 문제가 있었다.
이 상황에 대해 같은 팀원이 원하는 결과를 얻어
해당 코드를 정리해보고자 한다.
https://muja-coder.tistory.com/88
설치
npm i @nestjs/config @aws-sdk/client-s3 multer-s3
npm i -D @types/multer-s3 @types/multer
이중에 필요 없는 것도 있는 거 같은데 npm prune 해봐도 사라지는 게 없어서.. 일단 다 설치 ㄱㄱ
코드
common/utils/multer.options.factory.ts
import { MulterOptions } from '@nestjs/platform-express/multer/interfaces/multer-options.interface';
import { diskStorage } from 'multer';
import { extname } from 'path';
export const multerOptionsFactory = (): MulterOptions => {
return {
storage: diskStorage({
filename(req, file, cb) {
const imageExt = extname(file.originalname);
cb(null, `${Date.now()}${imageExt}`);
},
}),
};
};
제일 먼저 multer를 세팅해 준다.
filename의 cb에서 두 번째 인자로 전달되는 값이 나중에 DB에 넣을, S3에 저장할 파일명이 되니
변경하고 싶으면 이 부분을 변경하면 된다.
* 참고로 나는 한글 파일명 깨지는 문제를 회피하기 위해 그냥 현재 시간으로 파일명을 지어줬다.
s3.service.ts
import { ConfigService } from '@nestjs/config';
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
import { Injectable } from '@nestjs/common';
import { createReadStream } from 'fs';
@Injectable()
export class S3Service {
private readonly client: S3Client;
private readonly region: string = this.configService.get<string>('AWS_BUCKET_REGION');
private readonly bucket: string = this.configService.get<string>('AWS_BUCKET_NAME');
constructor(private readonly configService: ConfigService) {
this.client = new S3Client({
region: this.region,
credentials: {
accessKeyId: this.configService.get<string>('AWS_ACCESS_KEY_ID'),
secretAccessKey: this.configService.get<string>('AWS_SECRET_ACCESS_KEY'),
},
});
}
async putObject(file) {
const { path, filename } = file;
await this.client.send(
new PutObjectCommand({
Bucket: this.bucket,
Key: filename,
Body: createReadStream(path),
}),
);
// return `https://${this.bucket}.s3.${this.region}.amazonaws.com/${filename}`;
}
}
해당 클래스를 통해 S3에 파일을 저장할 수 있다.
위의 multer에서 설정한 filename이
putObject에서 전달받은 file 내부의 filename으로 꺼내져 사용되는 걸 확인할 수 있다.
.env
AWS_BUCKET_REGION=
AWS_BUCKET_NAME=
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
package.json과 같은 위치에 생성하면 되며
AWS에서 발급받은 값들을 맞는 위치에 넣어주면 된다.
boards.module.ts
@Module({
imports: [
TypeOrmModule.forFeature([Board]),
MulterModule.registerAsync({
imports: [ConfigModule],
useFactory: multerOptionsFactory,
inject: [ConfigService],
}),
],
controllers: [BoardsController],
providers: [BoardsService, S3Service]
})
export class BoardsModule {}
기본적인 Boards 관련 Module에
MulterModule과 S3Service Privoder가 추가된 모습
boards.controller.ts
@Post()
@UseInterceptors(FileInterceptor('file'))
createBoard(
@Body() boardData: CreateBoardDto,
@UploadedFile() file: Express.Multer.File,
) {
return this.boardsService.createBoard(boardData, file);
}
FormData로 전달받기 때문에 게시글의 title, content 등이 담긴 boardData와
이미지 파일이 있는 file을 같이 받는 모습이다.
관련 자세한 내용은 [링크] 참고
file에는 해당 이미지 파일의 데이터와 multer에서 세팅한 filename이 담겨있다.
boards.service.ts
@Injectable()
export class BoardsService {
constructor(
@InjectRepository(Board) private boardRepository: Repository<Board>,
private readonly s3Service: S3Service
) {}
async createBoard(boardData: CreateBoardDto, file: Express.Multer.File) {
if (!file) {
throw new BadRequestException('파일이 존재하지 않습니다.');
}
// 게시글 저장
boardData.imagePath = file.filename;
this.boardRepository.insert(boardData);
// S3 이미지 저장
await this.s3Service.putObject(file);
}
}
boardRepository.insert()를 통해 파일명이 담긴 게시글의 저장까지 완료되어야
비로소 S3로 저장이 된다.
만약 중간에 에러가 난다면 S3에 저장이 되지 않는다.
이전에는 미들웨어에서 이미 이미지 파일을 저장하고 service에 들어와
만약 service 로직이 실패한다면 S3에 저장한 이미지를 다시 지워야 해서
저장, 삭제 두 번의 AWS 요청이 있어야 됐는데
위 로직을 통해 실패 시 단 한 번의 요청 없이 처리할 수 있게 되었다.
정리
99%는 위에서 소개한 팀원이 만든 로직이다.
검색해서 나온 블로그의 도움으로
- 서버 내부에 저장할 때 원하는 시점에 저장하기
- S3에 저장할 때 Interceptor를 통해 저장하며 service 진입하기
두 단계까진 혼자 스스로 진행했었으나
Interceptor의 기능을 원하는 시점으로 돌릴 방법이 도저히 떠오르지 않는 상태에서
그냥 service를 만들어 쓰는 아주 간단한 방법으로 해결을 하신걸 보고 감탄하지 않을 수 없었다.
아직 이해가 가지 않는 코드들도 있어서
나중에 좀 더 Nest.js에 익숙해지면 다시 돌아와 어떤 원리로 위 로직이 가능했고
개선할 사항이 있진 않은지 체크해 보는 시간을 가져야 할 것 같다.
'Node.js' 카테고리의 다른 글
[Nest.js] 이메일 인증 시스템 (2). cache-manager (0) | 2023.02.23 |
---|---|
[Nest.js] 이메일 인증 시스템 (1). nodemailer (0) | 2023.02.23 |
[Nest.js] 캐싱 사용해보기 (0) | 2023.02.22 |
[Nest.js] FormData 전달받기 with multer (0) | 2023.02.22 |
[동시성 문제] Apache JMeter를 이용한 테스트 (0) | 2023.02.20 |