NestJs 파일업로드 이 글로 끝!

dev_leewoooo·2022년 6월 22일
2

NestJs

목록 보기
13/15
post-thumbnail

NestJs에서 File Upload하기

예제코드는 Github에 있습니다 :)

Goal

  • multer를 사용해보자

  • nestjs에서 file upload를 어떻게 처리할까?


Multer

MulterNode진영에서 파일 업로드에 이용되는 대표적인 라이브러리다. NestJs 공식 문서에서도 파일 업로드를 처리하기 위해 ExpressMulter 미들웨어를 사용하여 설명하고 있다.

NestJstypescript기반이기 때문에 Multer 라이브러리의 type 관련 라이브러리를 추가하는 것으로 시작한다. (yarn을 기준으로 작성)

yarn add @tyeps/multer -D

Upload하기

단일 파일

단일 파일을 업로드 받을 때 FileInterceptor()를 이용하게 되며 파라미터는 2개를 받는다.

  • multipart/form-data에서 name에 해당하는 값
  • Multer의 Option을 받는다. (Multer Option)
@Post('/upload')
@UseInterceptors(FileInterceptor('file'))
uploadFile(@UploadedFile() file: Express.Multer.File) {
  console.log(file);
}

위와 같이 작성 후 해당 endpoint로 API호출을 하게 되면 아래와 같이 데이터가 들어온다.

{
  fieldname: 'file',
  originalname: 'image.jpg',
  encoding: '7bit',
  mimetype: 'image/jpeg',
  buffer: <Buffer ... 939900 more bytes>,
  size: 939950
}

다중 파일

단일파일과 크게 다르지 않지만 사용하는 InterceptorParameterDecorator만 변경된다. 기존에는 단수형를 사용하였다면 이제는 복수형을 사용하면 된다.

FilesInterceptor를 이용할 때 두번 째 파라미터로 업로드가 가능한 파일의 갯수를 제한할 수 있다.

또 다른 옵션으로는 FileFieldsInterceptor를 사용할 수 있는데 multipart/form-data로 들어오는 name마다 maxCount를 부여할 수 있다.

마지막으로 AnyFilesInterceptor를 사용할 수 있는데 요청으로 들어오는 모든 name에 해당하는 파일을 받을 수 있게 된다.

@Post('/uploads')
@UseInterceptors(FilesInterceptor('files')) // FilesInterceptor
// @UseInterceptors(FilesInterceptor('files', 3)) // 업로드 파일을 3개로 제한
// @UseInterceptors(FileFieldsInterceptor([{ name: 'files', maxCount: 1 }])) // name 값이 files에 대한 limit
// @UseInterceptors(AnyFilesInterceptor()) // 모든 파일 받기
uploadFiles(@UploadedFiles() files: Array<Express.Multer.File>) { // UploadedFiles
  console.log(files);
}

위와 같이 작성 후 해당 endpoint로 API호출을 하게 되면 아래와 같이 데이터가 들어온다. 단일 업로드와 다른 점은 배열로 들어온다는 것이다.

[
  {
    fieldname: 'files',
    originalname: 'image.jpg',
    encoding: '7bit',
    mimetype: 'image/jpeg',
    buffer: <Buffer ... 939900 more bytes>,
    size: 939950
  },
  {
    fieldname: 'files',
    originalname: 'knowledge_graph_logo.png',
    encoding: '7bit',
    mimetype: 'image/png',
    buffer: <Buffer ... 10020 more bytes>,
    size: 10070
  }
]

옵션

위에서 설명한 것과 같이 FileInterceptor의 두번째 파라미터로 옵션을 부여할 수 있다. FilesInterceptor는 3번째 파라미터에 옵션이 들어간다.

사용되는 옵션은 Multer Option를 참조하면 자세히 볼 수 있다. 해당 document를 기준으로 글을 작성해 나갈 것이다.

만일 Option을 생락하면 파일은 디스크가 아니라 메모리에 저장되며 기본적으로 Multer이름의 중복을 방지하기 위해 내부적으로 임의로 이름을 생성하며 확장자는 붙어있지 않게 된다.

Multer Option은 아래와 같은 프로퍼티를 갖는다.

export interface MulterOptions {
  dest?: string;
  storage?: any;
  limits?: {...};
  preservePath?: boolean;
  fileFilter?(
    req: any,
    file: {...},
    callback: (error: Error | null, acceptFile: boolean) => void
  ): void;
}

dest

가장 기본적인 옵션인 destMulter에게 파일을 어디로 업로드 할 지를 알려준다.

dest를 지정하게 되면 지정된 위치에 업로드 된 파일이 저장이 되게 된다. 이 때 지정한 path에 디렉토리가 존재하지 않는다면 자동적으로 생성이 된며 확장자가 없는 상태에서 Multer 내부에서 임의로 생성한 이름으로 저장이 되게 된다.


storage

dest보다 조금 더 세밀하게 upload를 제어하고 싶을 때 사용할 수 있는 옵션이다. storage는 2가지가 존재한다. (현재 글은 disk storage를 기준으로 작성.)

  • DiskStorage

  • MemoryStorage

disk storage

storage에 정의할 수 있는 DiskStorageOptions는 아래와 같은 interface이다.

interface DiskStorageOptions {
  destination?: string | (( // 1
      req: Request,
      file: Express.Multer.File,
      callback: (error: Error | null, destination: string) => void
  ) => void) | undefined;
  filename?( // 2
      req: Request,
      file: Express.Multer.File,
      callback: (error: Error | null, filename: string) => void
  ): void;
}

// ex
const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    if(fs.existsSync('업로드 위치')) {
      fs.mkdirSync('업로드 위치', { recursive: true });
    }

    cb(null, '업로드 될 위치')
  },
  filename: function (req, file, cb) {
    // 파일명 조작 (ex: 임의의 이름 생성 + 확장자)
    cb(null, '파일명')
  }
})
  1. 파일이 저장될 위치를 지정한다. 만일 destination을 지정하지 않으면 운영체제 시스템에서 임시 파일을 저장하는 기본 디렉토리를 사용한다. 이전 dest를 사용했을 때와 다르게 디렉토리가 없으면 Error를 발생시키기 때문에 주의해야한다.

  2. 저장 될 파일의 이름을 결정한다. 만일 filename이 주어지지 않는다면 업로드 되는 파일은 확장자를 제외한 랜덤한 이름으로 지어진다. 그렇기 때문에 파일확장자를 직접 추가해야한다.


limit

속성들의 크기를 제한을 지정할 수 옵션이다. 사용할 수 있는 옵션은 아래와 같다.

속성설명기본값
fileNameSize필드명 사이즈 최대값100 bytes
fieldSize필드값 사이즈 최대값1MB
fields파일 형식이 아닌 필드의 최대 개수무제한
fileSizemultipart 형식 폼에서 최대 파일 사이즈 (bytes)무제한
filesmultipart 형식 폼에서 파일 필드의 최대 개수무제한
partsmultipart/form-data에서 파일과 필드의 갯수무제한
headerPairsmultipart 형식 폼에서 파싱할 헤더의 <key,value> 쌍의 최대 개수2000

fileFilter

fileFilter는 어느 파일을 업로드 할지, 혹은 건너뛸지 제어할 수 있다. fileFilter는 아래와 같은 syntax를 가지고 있다.

 fileFilter?(req: any, file: {
    fieldname: string;
    originalname: string;
    encoding: string;
    mimetype: string;
    size: number;
    destination: string;
    filename: string;
    path: string;
    buffer: Buffer;
}, callback: (error: Error | null, acceptFile: boolean) => void): void;

//ex
fileFilter: (req, file, cb) {
  // 이 함수는 boolean 값과 함께 `cb`를 호출함으로써 해당 파일을 업로드 할지 여부를 나타낼 수 있습니다.
  // 이 파일을 거부하려면 다음과 같이 `false` 를 전달합니다:
  cb(null, false)
  // 이 파일을 허용하려면 다음과 같이 `true` 를 전달합니다:
  cb(null, true)
  // 무언가 문제가 생겼다면 언제나 에러를 전달할 수 있습니다:
  cb(new Error('I don\'t have a clue!'))
}

NestJs에서 Multer 옵션 부여하기

Module에 Import하기

@nestjs/platform-express에서 제공하는 MulterOptionsFactory, MulterModuleAsyncOptions 인터페이스를 구현함으로 Multer 옵션을 생성하는 프로바이더를 생성할 수 있다.

MulterOptionsFactoryMulterModuleAsyncOptions의 인터페이스는 아래와 같다. 프로바이더를 작성할 때 configModule와 같은 것들을 이용하고자 하면 MulterModuleAsyncOptions를 이용하면 된다. (현재 글은 MulterOptionsFactory를 기준으로 작성한다.)

export interface MulterOptionsFactory {
  createMulterOptions(): Promise<MulterModuleOptions> | MulterModuleOptions;
}
export interface MulterModuleAsyncOptions
  extends Pick<ModuleMetadata, "imports"> {
  useExisting?: Type<MulterOptionsFactory>;
  useClass?: Type<MulterOptionsFactory>;
  useFactory?: (
    ...args: any[]
  ) => Promise<MulterModuleOptions> | MulterModuleOptions;
  inject?: any[];
}

MulterOptionsFactory를 구현하였다면 Module에서 import를 받을 수 있다.

@Injectable()
export class MulterConfigService implements MulterOptionsFactory {
  createMulterOptions(): MulterOptions {
    return { ... }
}


imports: [{
  MulterModule.registerAsync({
    useClass: MulterConfigService
  })
}]

Interceptor에 부여하기

Upload하기에서 Interceoptor의 파라미터로 MulterOption을 부여할 수 있다고 하였다.

MulterOptions에 맞는 형식의 Object를 만들어 넣어주어도 되며 혹은 MulterOptions을 return 하는 함수를 실행시켜주어도 된다.

const options: MulterOptions = {...}
function getMulterOptions() : MulterOptions { return {...} }

@Post('/upload')
@UseInterceptors(FileInterceptor('file',options))
@UseInterceptors(FileInterceptor('file',getMulterOptions()))
uploadFile(@UploadedFile() file: Express.Multer.File) {...}

REFERENCE

profile
leewoooo

0개의 댓글