안녕하세요! 오늘은 NestJS에서 AWS S3 파일 업로드를 구현하는 방법을 공유해보려고 해요.
저는 우아한형제들의 nestjs-library-config
를 사용해서 config 설정을 관리합니다 관련해서 다음에 정리하도록 할게요!
기존 방식의 문제점 🤔
보통 NestJS에서 S3 업로드를 구현할 때 multer-s3를 많이 사용하는데요, 이 방식에는 몇 가지 단점이 있습니다:
- S3Client 인스턴스를 여러 번 생성하게 됨
- 코드가 복잡해짐
- 테스트하기 어려움
개선된 방식 소개 ✨
1. 필요한 패키지 설치
npm install @nestjs/platform-express @aws-sdk/client-s3
2. Module 설정
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { S3Client } from '@aws-sdk/client-s3';
import { FileConfigService } from './file-config.service';
import { FileService } from './file.service';
import { FileController } from './file.controller';
@Module({
imports: [ConfigModule.forFeature(FileConfigService)],
providers: [
FileService,
{
provide: 'S3_CLIENT',
useFactory: (fileConfigService: FileConfigService) => {
return new S3Client({
credentials: {
accessKeyId: fileConfigService.accessKey,
secretAccessKey: fileConfigService.secretAccessKey,
},
region: fileConfigService.region,
});
},
inject: [FileConfigService],
},
],
controllers: [FileController],
exports: ['S3_CLIENT', FileService],
})
export class FileModule {}
3. Controller 작성
@Controller('files')
@UseGuards(JwtAuthGuard)
export class FileController {
constructor(private readonly fileService: FileService) {}
@Post('upload')
@UseInterceptors(FilesInterceptor('files', 5)) // 최대 5개 파일
async uploadFiles(
@CurrentUser() user: TokenPayload,
@UploadedFiles(
new ParseFilePipe({
validators: [
new FileTypeValidator({ fileType: /(jpg|jpeg|png|gif)$/ }),
new MaxFileSizeValidator({ maxSize: 5 * 1024 * 1024 }), // 5MB
],
}),
) files: Express.Multer.File[]
) {
return this.fileService.uploadFiles(files, user.sub);
}
@Get('list')
listFiles(@CurrentUser() user: TokenPayload) {
return this.fileService.listFiles(user.sub);
}
@Delete(':key')
deleteFile(
@CurrentUser() user: TokenPayload,
@Param('key') key: string
) {
return this.fileService.deleteFile(user.sub, key);
}
}
4. Service 구현
@Injectable()
export class FileService {
constructor(
@Inject('S3_CLIENT') private readonly s3: S3Client,
private readonly fileConfig: FileConfig,
) {}
/**
* 다중 파일 업로드 처리
* Promise.all을 사용하여 여러 파일을 병렬로 업로드
*/
async uploadFiles(files: Express.Multer.File[], userId: string) {
const uploadedFiles = await Promise.all(
files.map(async (file) => {
const ext = file.originalname.split('.').pop();
// 파일명 충돌 방지를 위해 타임스탬프와 랜덤값 추가
const key = `uploads/${userId}/${Date.now()}-${Math.random().toString(36).substring(7)}.${ext}`;
// S3에 파일 업로드
await this.s3.send(
new PutObjectCommand({
Bucket: this.fileConfig.bucket,
Key: key,
Body: file.buffer,
ContentType: file.mimetype,
}),
);
return {
originalname: file.originalname,
key,
size: file.size,
type: file.mimetype,
};
}),
);
return {
success: true,
files: uploadedFiles,
};
}
}
이렇게 구현하면 좋은 점 🌈
성능 최적화
- S3Client 인스턴스 재사용
- 병렬 업로드로 처리 속도 향상
코드 품질
- 책임 분리가 명확함 (컨트롤러는 검증, 서비스는 업로드 처리)
- 테스트하기 쉬움
- 코드가 간결해짐
확장성
- 다른 스토리지로 변경하기 쉬운 구조
- 추가 기능 구현이 용이
마무리 🎉
이렇게 해서 NestJS에서 S3 파일 업로드를 깔끔하게 구현하는 방법을 알아보았습니다.
기존 multer-s3 방식보다 더 깔끔하고 유지보수하기 좋은 코드가 되었고 s3 접근하는 방식도 명확해 진 거 같아서 만족스럽습니다.
혹시 궁금한 점이나 개선할 부분이 있다면 댓글로 알려주세요~ 😊
참고 자료
'개발 > NestJS' 카테고리의 다른 글
TypeORM에서 insert() 사용 시 @BeforeInsert가 동작하지 않는 문제 해결하기 🤔 (0) | 2024.11.20 |
---|---|
NestJS 설정 관리의 진화: nestjs-library-config 도입기 (4) | 2024.11.18 |
😎 NestJS class-transform 제대로 알고 쓰자! (3) | 2024.11.12 |
[NestJS] TypeORM Pagination: 설치, 사용법, 직접 구현과의 비교 with nestjs-paginate (1) | 2024.11.05 |
TypeORM 트랜잭션: typeorm-transactional 사용 전후 비교 (0) | 2024.10.29 |
댓글