복's
[ Nest JS ] File Upload 본문
이미지 파일들을 서버로 업로드할 일이 생겼다.
당장은 큰 용량의 이미지들이 올라가지 않기 때문에 S3 같은 스토리지 사용 대신에 서버 폴더에 업로드 하기로 했다.
[ 📌 목표 ]
- 이미지 업로드를 한 곳에서만 하지 않기 때문에 공통적으로 사용할 수 있도록 모듈화 하기
- 이미지 압축하기
- 이미지 관련한 예외 케이스 처리하기
- 파일 사이즈 제한하기
[ 📌 파일 업로드 하기 & 모듈화 ]
다행히도 좋은 자료가 많았지만 맨 처음으로 접한건 역시 Docs다.
기본적인 예시는 제공되기 때문에 먼저 파일을 전송 했을 때 파일이 받아지는지 확인 해본다.
$ npm i -D @types/multer
Multer를 사용하기 때문에 라이브러리 다운로드를 받고 시작한다.
@UseInterceptors(FileInterceptor('file'))
@Patch('/:todo_id/user/:user_id/image')
updateImage(
@Param('todo_id') todo_id: number,
@Param('user_id') user_id: number,
@UploadedFile() file: Express.Multer.File
){
this.logger.debug("todo_id", todo_id);
this.logger.debug("user_id", user_id);
this.logger.debug(file);
return this.groupService.updateImage(todo_id, user_id, file);
}
Docs가 제공하는 기본적인 형태를 그대로 작성 해보았다.
과연 logger에서 찍히는 file은 Docs와 같은 결과가 나오나? (요청은 postman을 통해서 진행)
설정을 전역적으로 등록해서 사용하는데에는 여러 방법이 있는데, MulterOptionsFactory 인터페이스를 구현한 클래스를 Config를 사용하는 하는 방법이다.
MulterModule.registerAsync({
useClass: MulterConfigService,
});
Docs에서 제공하는 MulterModule이 요구하는 option object이다.
import { HttpStatus, Injectable, Logger } from "@nestjs/common";
import { MulterOptionsFactory } from "@nestjs/platform-express";
import * as multer from 'multer';
import * as path from 'path';
import * as fs from 'fs';
import { DoWithException } from "src/do-with-exception/do-with-exception";
@Injectable()
export class MulterConfig implements MulterOptionsFactory{
dir_path: string;
constructor(){
this.dir_path = process.env.IMAGE_PATH;
this.mkdir();
}
mkdir(){
try{
fs.readdirSync(this.dir_path);
} catch(err){
fs.mkdirSync(this.dir_path);
}
}
changePath(dir_path: string){
this.dir_path = dir_path;
}
createMulterOptions(){
const dir_path = this.dir_path;
const option = {
storage: multer.diskStorage({
destination(req, file, done){
done(null, dir_path);
},
filename(req, file, done){
const allowed_exts = ['.jpg', '.jpeg', '.png', '.gif'];
const ext = path.extname(file.originalname);
const name = path.basename(file.originalname, ext);
if(!allowed_exts.includes(ext.toLowerCase())) {
done(new DoWithException('지원하지 않는 파일 확장자입니다.', '1000', HttpStatus.BAD_REQUEST), file.originalname);
}
done(null, `${name}_${Date.now()}${ext}`);
}
}),
limits: { fileSize: 1 * 1024 * 1024 } // 1 MB
};
return option;
}
}
- 선언부: implements MulterOptionsFactory를 통해서 요구한 인터페이스 구현
- 생성자: file upload path 설정
- mkdir: 폴더가 없는 경우 폴더를 생성하는 코드인데 미리 만들고 안써도 상관은 없을 것 같다.
- changePath: 모듈을 사용하는 코드가 여러개라서 경로를 다르게 하고 싶었다.
- createMulterOptions: 옵션들
- storage: file을 저장할 공간으로 메모리가 아닌 디스크 공간에 저장하도록 했다.
- filename: file 이름을 설정하는 공간인데 나는 여기서 확장자에 따라서 예외를 처리했다. (본래라면 pipe를 사용해서 하는거 같은데.. 음..) -> 변경이 필요한 부분은 맞는거 같다. (pipe에서 validation이 이뤄지고 예외는 400 Bad Request로 던져주는 것 같다.)
- limits: 파일 최대 크기를 설정했다. (1 MB)
- storage: file을 저장할 공간으로 메모리가 아닌 디스크 공간에 저장하도록 했다.
import { Module } from '@nestjs/common';
import { MulterModule } from '@nestjs/platform-express';
import { MulterConfig } from './fileUpload/MulterConfigService';
@Module({
imports: [
MulterModule.registerAsync({
useClass: MulterConfig
})
],
exports: [ MulterModule ]
})
export class UtilsModule {}
그리고 만든 옵션을 적용한 모듈을 등록하면 된다.
[ 📌 이미지 압축 ]
멘토링 받다가 이미지 압축 관련 라이브러리로 sharp를 듣게 되었다.
노드 진영에서 오랫동안 살아남은 성능이 좋은 라이브러리 보였다.
현재 프로젝트에서 3D 렌더링도 하고있기 때문에 다른 resource로 부터의 자원 소모를 줄이는데 도움이 될 것 같았고, 서버에 올리는 이미지 중에서 프로필 같이 확대도 안되고 딱히 큰 용도가 없는 이미지는 압축해도 괜찮을거 같다는 생각이 들었다.
$ npm install sharp
사실 제공하는 옵션은 많지만 나는 딱히 사용의 필요성은 못 느꼈고,
try {
const filePath = file.path;
await sharp(filePath).resize({ width: 700, height: 700, fit: 'contain' }) // 원하는 크기로 조정
.toFile(filePath, async(err, info) => {
try{
await fs.unlink(filePath);
} catch(err){
// 원본 삭제 에러
}
});
} catch(err) {
// 리사이징 에러
}
에러에 대해서 처리는 아직 하지 않았지만 만들어놓은 에러로 wrapping해서 던져야겠다라고 생각하고 주석만 처리해놨다.
크기는 660KB 파일 넣으면 34KB 까지 줄어 드는데 파일 용도에 따라서 얼만큼 줄일지는 잘 생각 해봐야겠다.
[ 📌 Reference ]
https://docs.nestjs.com/techniques/file-upload#file-validation
Documentation | NestJS - A progressive Node.js framework
Nest is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with TypeScript and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Rea
docs.nestjs.com
https://sharp.pixelplumbing.com/
sharp - High performance Node.js image processing
sharp.pixelplumbing.com
'교육 > SW 정글 프로젝트(나만무)' 카테고리의 다른 글
[ Nest JS ] Pagination - 페이지네이션(페이징) (1) | 2023.12.18 |
---|---|
[ Nest JS ] Interceptor / Wrapping Exception (0) | 2023.11.26 |
[ Nest JS ] 예외 클래스 만들기 및 필터에서 예외 받기 (1) | 2023.11.16 |
[ Nest JS ] 예외 처리 (Exception Handling) 및 예외 필터(Exception filters) (1) | 2023.11.15 |
[ Nest JS ] 미들웨어(Middleware) 설정 (0) | 2023.11.15 |