[Nest.js] 파일 업로드 (1) - Local에 저장

Hoplin·2023년 7월 9일
0
post-thumbnail

파일 업로드

Express에서 파일 업로드시 Multer라는 패키지를 사용하였다. Nest.js 또한 Multer를 사용하여 파일을 저장한다. 로컬에 저장하는 방법, AWS S3에 저장하는 방법 두가지를 살펴볼 것이며, 우선 이 포스트에서는 단일 파일을 Local에 저장하는 방법을 살펴본다.

공식문서 : https://docs.nestjs.com/techniques/file-upload

MulterModule 설정

먼저 MulterModule을 설정한다. register메소드를 통해 등록을 해주는데, 이 메소드에 들어가는 옵션은 Express의 Multer 옵션과 동일하니 참고하자. 필요에 따라 다른 프로바이더를 주입받아 쓸 수 있는 registerAsync도 사용할 수 있다.

multer의 readme를 보면 아래와 같은 옵션을 지정할 수 있다.

  • dest / storage
  • fileFilter
  • limits
  • preservePath(우선 제외)

이중 fileFilterlimits는 Nest.js가 Pipe로 제공하는것이 있기에 생략하고 storage만 지정해준다.


//multer.option.ts
export default (): MulterOptions => {
  return {
    storage: diskStorage({
      destination: (req, file, cb) => {
        cb(null, `${__dirname}/../../upload`);
      },
      filename: (req, file, cb) => {
        cb(null, `${new Date().toISOString()}_${file.originalname}`);
      },
    }),
  };
};

// MulterModule2.ts
@Module({
  imports: [MulterModule.register(multerOption())],

MulterOption의 storage에는 두가지를 설정해 주어야 한다. 파일을 저장할 기본 위치 그리고 파일 이름을 어떻게 저장할것인지를 지정해 주어야 한다.

문서 : https://github.com/expressjs/multer#storage

주의사항은 아래 두가지가 있다.

  • destination에 지정한 디렉토리는 사용자가 미리 생성해 주어야 한다. 존재하지 않는 디렉토리인 경우, 오류가 난다.
  • filename지정시, multer는 확장자를 따로 붙여주지 않는다. 사용자가 직접 확장자를 파일 이름에 명시해 주어야 한다.

Controller 정의

이제 파일을 받는 컨트롤러를 정의한다. 기본적으로 form data를 받는 컨트롤러이므로, Http 메소드는 POST로 지정한다.

Nest.js에서는 파일을 받기 위해서는 FileInterceptorUploadedFile을 활용한다. FileInterceptor는 UploadedFile을 활용하여, Request로 부터 파일을 추출한다

@UseInterceptors(FileInterceptor('file'))
  @Post('/local')
  public multerLocal(@UploadedFile() file: Express.Multer.File) {

FileInterceptor는 두개의 매개변수를 받는다.

  • 파일을 제공하는 HTML form의 이름. 여기서는 form의 이름을 file이라고 가정한다.
  • Multer Option, 이는 앞에서 MulterModle을 import하면서 정의하였기에 생략한다.

File Validation

파일에 대해 검증을 해야하는 경우도 있다. 가장 대표적으로 파일의 크기와, 파일의 확장자를 예로 들 수 있다. Nest.js는 파일의 크기를 검증하는MaxFileSizeValidator, 파일의 확장자를 검증하는FileTypeValidator 총 두가지의 검증 클래스를 제공한다. FileTypeValidator는 파일의 내용에 대한 검증을 하지 못한다는것에 유의하자.

UploadedFile()안에 ParseFilePipe() 혹은 ParseFilePipeBuilder()를 통해 사용할 수 있다. 다만 주의할 점은 ParseFilePipe()를 사용하게 되면, ParseFilePipe()의 매개변수 옵션에 validate라는 필드에 배열형태로 Validator들을 정의해줄 수 있다.

ParseFilePipeBuilder()를 사용하면 위의 방식과 살짝 다르다. FileTypeValidator 대신 addFileTypeValidator 메소드를, MaxFileSizeValidator대신 addMaxSizeValidator 메소드를 사용한다.

FileTypeValidator,addFileTypeValidator은 옵션으로 fileType을 받으며, 코드의 주석에 적혀있듯이 파일 이름의 정규표현식을 받는다.

MaxFileSizeValidatoraddMaxSizeValidatormaxSize옵션을 받으며, byte단위 값을 입력해주어야한다.

ParseFilePipeBuilder()끝에는 build메소드를 호출해 주어야 한다. 기본적으로 ParseFileOption에서 validate필드를 제외한 값을 받는다.

코드 원본 : https://github.com/nestjs/nest/blob/master/packages/common/pipes/file/parse-file-pipe.builder.ts

ParseFileOption에는 아래와 같이 errorHttpStatusCodeexceptionFactory를 넘겨줄 수 있다.

원본 코드 : https://github.com/nestjs/nest/blob/master/packages/common/pipes/file/parse-file-options.interface.ts

errorHttpStatusCode는 Nest.js에 내장된 Http오류코드를 반환해줄 수 있으며, exceptionFactory는 커스텀 예외객체를 반환해줄 수 있다.Nest.js 내장 Http오류는 아래 원본 코드를 참고하자.

원본 코드: https://github.com/nestjs/nest/blob/master/packages/common/utils/http-error-by-code.util.ts

원본 코드 : https://github.com/nestjs/nest/blob/master/packages/common/enums/http-status.enum.ts

Custom File Validator

직접 만들지 않겠지만, 필요에 따라 Custom File Validator를 만들 수 도 있다. 구현을 위해서는 FileValidator 추상클래스를 상속받아야 한다.

원본 코드 : https://github.com/nestjs/nest/blob/master/packages/common/pipes/file/file-validator.interface.ts

위 코드에서 나오는 IFile 타입은 아래와 같다.

원본 코드 : https://github.com/nestjs/nest/blob/master/packages/common/pipes/file/interfaces/file.interface.ts#L1

Controller 작성

  @UseInterceptors(FileInterceptor('file'))
  @Post('/local')
  public multerLocal(
    @UploadedFile(
      new ParseFilePipeBuilder()
        .addFileTypeValidator({
          fileType: '.(jpg|png)$',
        })
        .addMaxSizeValidator({
          maxSize: 1000000,
        })
        .build({
          errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY,
        }),
    )
    file: Express.Multer.File,
  ){
    ...

이 예시에서는 .jpg,.png로 끝나는 이미지 파일만 받으며, 파일 최대 크기는 10MB로 설정하였다. 그리고 errorHttpStatusCode를 통해 예외 메세지를 Unprocessable Entity로 설정해주었다.

Postman을 통한 실험

이제 Postman을 통해 파일이 잘 전송/저장되는지 확인해 본다. Bodyform-data로 변경해준 뒤, 필드 이름을 file로 지정한다. FileInterceptor에 Form 이름을 file로 해주었기 때문이다.
Request Body file필드의 데이터타입을 File로 변경해 주어야 한다.

지정한 경로에 잘 생성된것을 볼 수 있다.

profile
더 나은 내일을 위해 오늘의 중괄호를 엽니다

0개의 댓글