NestJS 공식문서 File upload

GGAE99·2023년 8월 15일
0

NestJS 공식 문서

목록 보기
22/33
post-thumbnail

File upload

파일 업로드 처리를 다루기 위해 Nest는 Express의 multer 미들웨어 패키지를 기반으로한 내장 모듈을 제공합니다. Multer는 주로 HTTP POST 요청을 통해 파일을 업로드하기 위해 사용되는 multipart/form-data 형식의 데이터를 처리합니다. 이 모듈은 사용자의 어플리케이션에 맞게 옵션을 설정하여 사용할 수 있습니다.

Warning!!
Multer는 지원되지 않는 멀티파트 형식(multipart/form-data)이 아닌 데이터를 처리할 수 없습니다. 또한, 이 패키지는 FastifyAdapter와 호환되지 않습니다.

더 나은 타입 안정성을 위해, Multer 타입 패키지를 설치합니다:

$ npm i -D @types/multer

이 패키지를 설치함으로써, 우리는 Express.Multer.File 타입을 사용할 수 있습니다. (다음과 같이 타입을 임포트할 수 있습니다 import { Express } from 'express').

Basic example

단일 파일을 업로드하려면, FileInterceptor() 인터셉터를 라우트 핸들러에 연결하고 @UploadedFile() 데코레이터를 사용하여 요청에서 파일을 추출하면 됩니다.

@Post('upload')
@UseInterceptors(FileInterceptor('file'))
uploadFile(@UploadedFile() file: Express.Multer.File) {
  console.log(file);
}

Hint!
FileInterceptor() 데코레이터는 @nestjs/platform-express 패키지에서 export 됩니다. @UploadedFile() 데코레이터는 @nestjs/common 패키지에서 export 됩니다.

FileInterceptor() 데코레이터는 2개의 인수를 받습니다:

  • fieldName: 파일이 있는 HTML form에서 제공하는 필드 이름
  • options: (선택적) MulterOptions 타입의 객체입니다. multer 생성자에서 사용되는 것과 같은 객체입니다. ( 자세히 알아보기는 여기 )

Warning!!
FileInterceptor() 는 Google Firebase 등의 타사 클라우드 공급자와 호환되지 않을 수 있습니다.

File validation

때로는 파일 크기나 파일 MIME 타입과 같은 들어오는 파일 메타데이터를 검증하는 것이 유용할 수 있습니다. 이를 위해 자체적으로 파이프를 생성하고 UploadedFile 데코레이터로 주석이 달린 매개변수에 바인딩할 수 있습니다. 아래 예제는 기본 파일 크기 유효성 검사 파이프를 구현하는 방법을 보여줍니다

import { PipeTransform, Injectable, ArgumentMetadata } from '@nestjs/common';

@Injectable()
export class FileSizeValidationPipe implements PipeTransform {
  transform(value: any, metadata: ArgumentMetadata) {
    // "value"는 파일의 속성과 메타데이터를 포함하는 객체입니다.
    const oneKb = 1000;
    return value.size < oneKb;
  }
}

Nest는 일반적인 사용 사례를 처리하고 새로운 사용 사례를 추가하는 데 도움이 되는 내장 파이프를 제공합니다. 이 파이프는 ParseFilePipe라고 하며, 다음과 같이 사용할 수 있습니다

@Post('file')
uploadFileAndPassValidation(
  @Body() body: SampleDto,
  @UploadedFile(
    new ParseFilePipe({
      validators: [
        //  ... 여기에 파일 유효성 검사 인스턴스를 설정합니다.
      ]
    })
  )
  file: Express.Multer.File,
) {
  return {
    body,
    file: file.buffer.toString(),
  };
}

보시다시피, ParseFilePipe에서 실행될 파일 유효성 검사기의 배열을 지정해야 합니다. 이 파이프에는 두 개의 추가적인 선택적 옵션도 있습니다는 것 또한 알아두면 좋습니다.

  • errorHttpStatusCode: 어떤 유효성 검사도 실패할 경우 던질 HTTP 상태 코드입니다. 기본값은 400 (BAD REQUEST)입니다.
  • exceptionFactory: 오류 메시지를 받아오고 오류를 반환하는 팩토리입니다.

이제 FileValidator 인터페이스로 돌아가봅시다. 이 파이프와 유효성 검사기를 통합하려면 내장 구현을 사용하거나 사용자 정의 FileValidator를 제공해야 합니다. 다음 예제를 참조하세요

export abstract class FileValidator<TValidationOptions = Record<string, any>> {
  constructor(protected readonly validationOptions: TValidationOptions) {}

  /**
   * 생성자에서 전달된 옵션에 따라 이 파일이 유효한지 여부를 나타냅니다.
   * @param file 요청 객체에서 가져온 파일
   */
  abstract isValid(file?: any): boolean | Promise<boolean>;

  /**
   * 검증 실패 시 오류 메시지를 생성합니다.
   * @param file 요청 객체에서 가져온 파일
   */
  abstract buildErrorMessage(file: any): string;
}

Hint!
FileValidator 인터페이스는 isValid 함수를 통해 비동기 유효성 검사를 지원합니다.
type 보안을 활용하기 위해 express(기본값)를 드라이버로 사용하는 경우 file 매개변수를 Express.Multer.File로 입력할 수도 있습니다.

FileValidator는 파일 객체에 접근하고 클라이언트가 제공한 옵션에 따라 유효성을 검사하는 일반 클래스입니다. Nest에는 프로젝트에서 사용할 수 있는 두 가지 내장 FileValidator 구현이 있습니다

  • MaxFileSizeValidator: 주어진 파일의 크기가 제공된 값보다 작은지 확인합니다 (바이트 단위).
  • FileTypeValidator: 주어진 파일의 MIME 타입이 주어진 값과 일치하는지 확인합니다.

Warning!!
파일 유형을 확인하기위해 FileTypeValidator 클래스는 multer에서 감지한 유형을 사용합니다. 기본적으로 multer는 사용자 기기의 파일 확장자에서 파일 유형을 유추합니다. 그러나 실제 파일 내용은 확인하지 않습니다. 파일은 임의의 확장자로 이름을 변경할 수 있으므로, 더 안전한 솔루션이 필요한 경우 파일의 마법 번호(magic number)를 확인하는 사용자 정의 구현을 고려해야 합니다.

이러한 유효성 검사기를 앞서 언급한 FileParsePipe와 어떻게 함께 사용하는지 이해하기 위해, 마지막 예제의 일부를 수정하여 설명하겠습니다

@UploadedFile(
  new ParseFilePipe({
    validators: [
      new MaxFileSizeValidator({ maxSize: 1000 }),
      new FileTypeValidator({ fileType: 'image/jpeg' }),
    ],
  }),
)
file: Express.Multer.File,

Hint!
파일 유효성 검사기의 수가 크게 늘어나거나 옵션들이 파일을 지저분하게 만드는 경우, 이 배열을 별도의 파일에 정의하고 fileValidators 같은 이름의 상수로 가져올 수 있습니다.

마지막으로, ParseFilePipeBuilder 클래스를 사용하여 유효성 검사기를 구성 및 생성할 수 있는 특별한 클래스도 있습니다. 아래와 같이 사용하여 각 검증기를 수동으로 인스턴스화하는 것을 피하고 옵션을 직접 전달할 수 있습니다:

@UploadedFile(
  new ParseFilePipeBuilder()
    .addFileTypeValidator({
      fileType: 'jpeg',
    })
    .addMaxSizeValidator({
      maxSize: 1000
    })
    .build({
      errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY
    }),
)
file: Express.Multer.File,

Array of files

파일로 이루어진 배열을 업로드 하기 위해 (하나의 필드 이름으로 되어있는), FilesInterceptor() 데코레이터를 사용합니다. (데코레이터 이름이 복수형인 "Files"인 부분에 주목하세요). 이 데코레이터는 세 가지 인수를 받습니다

  • fieldName : 파일이 있는 HTML form에서 제공하는 필드 이름.
  • maxCount : (선택적) 허용 할 최대 파일의 수
  • options : (선택적) MulterOptions 객체, 위의 설명과 동일.

FilesInterceptor()를 사용할 때는 @UploadedFiles() 데코레이터를 사용하여 요청에서 파일을 추출합니다.

@Post('upload')
@UseInterceptors(FilesInterceptor('files'))
uploadFile(@UploadedFiles() files: Array<Express.Multer.File>) {
  console.log(files);
}

Hint!
FilesInterceptor() 데코레이터는 @nestjs/platform-express 패키지에서 export 됩니다. @UploadedFiles() 데코레이터는 @nestjs/common 패키지에서 export 됩니다.

Multiple files

여러 파일을 업로드하려면 (모두 서로 다른 필드 이름 키로), FileFieldsInterceptor() 데코레이터를 사용하면 됩니다. 이 데코레이터는 두 가지 인수를 받습니다

  • uploadedFields: 객체의 배열로, 각 객체는 필드 이름을 지정하는 문자열 값으로 name 속성을 필수로 가져야 하며, 위에서 설명한 대로 maxCount 속성은 선택적입니다.
  • options: 위에서 설명한 선택적인 MulterOptions 객체

FileFieldsInterceptor()를 사용할 때는 @UploadedFiles() 데코레이터를 사용하여 request에서 파일을 추출합니다.

@Post('upload')
@UseInterceptors(FileFieldsInterceptor([
  { name: 'avatar', maxCount: 1 },
  { name: 'background', maxCount: 1 },
]))
uploadFile(@UploadedFiles() files: { avatar?: Express.Multer.File[], background?: Express.Multer.File[] }) {
  console.log(files);
}

Any Files

모든 필드를 임의의 필드 이름 키로 업로드하려면 AnyFilesInterceptor() 데코레이터를 사용하면 됩니다. 이 데코레이터는 위에서 설명한 대로 선택적인 options 객체를 받을 수 있습니다.

AnyFilesInterceptor()를 사용할 때는 @UploadedFiles() 데코레이터를 사용하여 request에서 파일을 추출합니다.

@Post('upload')
@UseInterceptors(AnyFilesInterceptor())
uploadFile(@UploadedFiles() files: Array<Express.Multer.File>) {
  console.log(files);
}

위의 코드에서 files 객체는 각 필드 이름 키에 해당하는 파일 배열을 포함하며, 이를 통해 모든 업로드된 파일들을 추출하고 로깅할 수 있습니다.

No files

multipart/form-data를 받아들이지만 파일 업로드를 허용하지 않으려면 NoFilesInterceptor를 사용합니다. 이렇게 하면 멀티파트 데이터가 요청 본문의 속성으로 설정됩니다. 요청과 함께 보내진 모든 파일은 BadRequestException을 발생시킵니다.

@Post('upload')
@UseInterceptors(NoFilesInterceptor())
handleMultiPartData(@Body() body) {
  console.log(body)
}

Default options

Multer 옵션을 파일 인터셉터에서 위에서 설명한 대로 지정할 수 있습니다. 기본 옵션을 설정하려면 MulterModule을 가져올 때 정적인 register() 메서드를 호출하여 지원되는 옵션을 전달하면 됩니다. 여기에 나열된 모든 옵션을 사용할 수 있습니다.

import { Module } from '@nestjs/common';
import { MulterModule } from '@nestjs/platform-express';

@Module({
  imports: [
    MulterModule.register({
      dest: './upload',
    }),
  ],
})
export class AppModule {}

Hint!
MulterModule 클래스는 @nestjs/platform-express 패키지에서 내보내집니다.

이를 통해 앱 전체에서 기본 업로드 디렉토리나 다른 옵션을 설정할 수 있습니다.

Async configuration

정적으로 옵션을 설정하는 대신에 MulterModule 옵션을 비동기적으로 설정해야 할 때는 registerAsync() 메서드를 사용합니다. 대부분의 동적 모듈과 마찬가지로, Nest는 비동기 구성을 다루기 위한 여러 가지 기술을 제공합니다.

첫번째로 팩토리 함수를 사용하는 것입니다

MulterModule.registerAsync({
  useFactory: () => ({
    dest: './upload',
  }),
});

다른 팩토리 프로바이더와 마찬가지로, 팩토리 함수는 비동기적일 수 있으며, inject를 통해 의존성을 주입할 수 있습니다.

MulterModule.registerAsync({
  imports: [ConfigModule],
  useFactory: async (configService: ConfigService) => ({
    dest: configService.get<string>('MULTER_DEST'),
  }),
  inject: [ConfigService],
});

또는 팩토리 대신 클래스를 사용하여 MulterModule을 구성할 수도 있습니다

MulterModule.registerAsync({
  useClass: MulterConfigService,
});

위의 구성에서는 MulterConfigServiceMulterModule 내부에서 인스턴스화하여 필요한 옵션 객체를 생성합니다. 이 예시에서 MulterConfigServiceMulterOptionsFactory 인터페이스를 구현해야 합니다. MulterModule은 제공된 클래스의 인스턴스화된 객체에서 createMulterOptions() 메서드를 호출할 것입니다.

@Injectable()
class MulterConfigService implements MulterOptionsFactory {
  createMulterOptions(): MulterModuleOptions {
    return {
      dest: './upload',
    };
  }
}

MulterModule 내부에서 별도의 사본을 생성하는 대신 기존 옵션 프로바이더를 재사용하려면 useExisting 구문을 사용하세요.

MulterModule.registerAsync({
  imports: [ConfigModule],
  useExisting: ConfigService,
});

질문 및 생각

  • MIME이란?
    Multipurpose Internet Mail Extensions의 약자로 간단히 말하면 파일 변환을 의미한다.
    현재는 웹을 통해 여러 형태의 파일을 전달하는데 사용하고 있지만 이 용어가 생길 땐 이메일과 함께 동봉할 파일을 텍스트 문자로 전환해서 이메일 시스템을 통해 전달하기 위해 개발되어 Internet Mail Extensions라고 불리기 시작했다고 한다.
    ASCII만으로는 전송이 안되는 음악, 워드 파일 등을 기존 시스템에서 문제 없이 전달하기 위해서는 텍스트로의 변환이 필요했다. 그래서 MIME을 사용한다.

  • magic number
    마법 번호는 파일 유형을 나타내는 숫자나 문자열 상수입니다. 이 숫자는 파일의 처음 512바이트에 있습니다. 기본적으로 지역화된 마법 파일 /usr/lib/locale/locale/LC_MESSAGES/magic이 파일의 마법 번호를 식별하는 데 사용됩니다. 지역화된 마법 파일이 없는 경우 /etc/magic 파일이 사용됩니다. 명령 줄에서 사용자는 "File script"와 같은 명령을 실행할 수 있으며, 이 명령은 "script: executable shell script"와 같은 출력을 생성합니다.

MulterOption

  • dest (string):
    업로드된 파일을 저장할 디렉토리 경로를 지정합니다. 파일은 서버의 파일 시스템에 저장됩니다.
  • storage (StorageEngine):
    파일 저장 방법을 지정하는 커스텀 스토리지 엔진을 설정할 수 있습니다. dest와 함께 사용되는 대신 커스텀 로직을 구현할 때 유용합니다.
  • fileFilter (function):
    업로드할 파일을 필터링하는 함수를 제공할 수 있습니다. 이 함수에서 원하는 파일 형식만 허용하거나 거부할 수 있습니다.
  • limits (object):
    업로드할 파일의 크기 및 파일 개수를 제한할 수 있는 옵션입니다.
    • fieldSize: 필드의 최대 바이트 크기를 제한합니다.
    • fileSize: 업로드할 파일의 최대 바이트 크기를 제한합니다.
    • files: 여러 파일 업로드 시 최대 파일 개수를 제한합니다.
  • preservePath (boolean):
    파일의 경로를 보존할 지 여부를 설정합니다. 기본값은 false이며, true로 설정하면 클라이언트가 제공한 파일 경로를 그대로 사용합니다.
  • onFileUploadStart (function):
    파일 업로드가 시작될 때 호출되는 콜백 함수입니다.
  • onFileUploadData (function):
    파일 업로드 중에 파일 데이터가 수신될 때 호출되는 콜백 함수입니다.
  • onFileUploadComplete (function):
    파일 업로드가 완료될 때 호출되는 콜백 함수입니다.
  • rename (function):
    업로드된 파일의 이름을 변경하는 데 사용되는 함수입니다.
  • limits (object):
    업로드에 대한 제한 사항을 설정합니다.
    • fieldSize: 필드 데이터의 최대 크기를 제한합니다.
    • fileSize: 업로드할 파일의 최대 크기를 제한합니다.

내 정리

  • Basic
    필드 명을 설정하고, 파일을 가져올 수 있다.
  • array
    같은 필드명을 사용하는 여러 파일을 배열 형태로 가져올 수 있다.
  • multiple
    여려개의 필드명을 사용하여 각각의 파일을 가져올 수 있다.
  • any
    필드명에 제한을 두지않고, 입력된 키값을 가져와서 필드명으로 사용한다.

오류

@Post('upload/no')
@UseInterceptors(NoFilesInterceptor())
handleMultiPartData(@Body() body) {
  console.log(body)
}
//NoFilesInterceptor가 @nestjs/platform-express에 없읍

1개의 댓글

comment-user-thumbnail
2023년 8월 15일

개발자로서 배울 점이 많은 글이었습니다. 감사합니다.

답글 달기