NestJS 帶你飛! 시리즈 번역 20# File Upload

2023. 6. 16. 21:19개발 문서 번역/NestJS

728x90
이 포스팅은 「NestJS 기초실무 가이드 : 강력하고 쉬운 Node.js 웹 프레임워크로 웹사이트 만들기」
(서명: NestJS 基礎必學實務指南:使用強大且易擴展的 Node.js 框架打造網頁應用程式)
책이 출간되었습니다.

 

파일 업로드(File Upload, 檔案上傳)는 기본중의 기본 기능이죠. 어디를 가도 파일 업로드 기능은 필수일겁니다.

(ex: SNS에서 프로필 사진 업로드, 동영상 업로드 등..)

 

Nest는 파일 업로드와 관련된 라이브러리 multer를 제공합니다.

multipart/form-data의 자료를 처리해주며 Express에서도 물론 해당 라이브러리의 존재를 확인할 수 있습니다.

엄청 유명한 라이브러리죠.

 

Multer 사용해보기

Nest에서는 multer가 내장되어있지만 multer의 type을 정의해주는 파일도 설치하시길 권장합니다.

npm을 통해 설치할 수 있습니다.

$ npm install @types/multer -D

단일 파일 업로드

단일 파일 업로드 방식은 간단합니다. 라우터 아래 FileInterceptor와 파라미터 데코레이터 @UploadedFile을 통해 파일을 얻어올 수 있습니다. FileInterceptor에는 2개의 파라미터가 들어갈 수 있으며, 다음과 같습니다.

1. fieldName : form에서 전송되는 key와 동일해야함

2. options : MulterOption과 대응됩니다. 자세한 내용은 multer의 공식 사이트를 참고해주세요.

 

아래는 app.controller.ts의 예제로, 단일 파일 업로드를 진행해보겠습니다.

import { Controller, Post, UploadedFile, UseInterceptors } from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';

@Controller()
export class AppController {

  @Post('/single')
  @UseInterceptors(FileInterceptor('file'))
  uploadSingleFile(@UploadedFile() file: Express.Multer.File) {
    return file;
  }
  
}

 

http://127.0.0.1:3000으로 파일 하나를 전송하면 다음과 같은 결과를 확인할 수 있습니다.

단일 필드 다중 파일 업로드

같은 field에서 1개 이상의 파일을 업로드 하고 싶을 때가 있습니다.
FilesInterceptor와 파라미터 데코레이터 @UploadedFiles를 사용하면 하나의 form에 있는 다중 파일들을
배열 형식으로 받아올 수 있습니다.

주의: 다중 파일은 Files 복수를 사용합니다. 단수로 입력하지 않도록 유의해주세요.
FileInterceptor@UploadedFile이 아닙니다.

 

FilesInterceptor는 3개의 파라미터를 채워넣을 수 있으며 아래는 그 예입니다.

1. fieldName : form에서 전송되는 key와 동일해야함

2. maxCount : 한번의 요청에서 받을 수 있는 파일 개수를 제한할 수 있으며 선택입력입니다.

3. options : MulterOption과 대응됩니다. 자세한건 multer의  공식 사이트를 참고해주세요.

 

아래는 app.controller.ts의 예제이며, 단일 form 다중 파일 업로드를 진행해보겠습니다.

 

import { Controller, Post, UploadedFiles, UseInterceptors } from '@nestjs/common';
import { FilesInterceptor } from '@nestjs/platform-express';

@Controller()
export class AppController {

  @Post('/multiple')
  @UseInterceptors(FilesInterceptor('files'))
  uploadMultipleFiles(@UploadedFiles() files: Express.Multer.File[]) {
    return files.map(({ fieldname, originalname }) => ({ fieldname, originalname }));
  }

}

127.0.0.1:3000/multiple에 요청을 보내면
map을 통해 해당 파일들이 배열 형식으로 반환이 된 결과를 확인할 수 있습니다.

 

다중 필드 다중 파일 업로드

다중 field에서 각각 1개 이상의 파일을 업로드 하고자 할 경우 FileFieldsInterceptor
파라미터 데코레이터 @UploadedFiles로 해당 필드 이름의이 key로 사용된 Express.Multer.File 형식의 다중 배열을 받아올 수 있습니다.

FileFieldsInterceptor에는 2개의 파라미터가 들어갑니다.

1. uploadedFields: name 속성을 사용해 필드의 이름을 지정할 수 있으며, 객체가 포함되어있는 배열입니다.

2. options: MulterOption에 대응됩니다.

 

app.controller.ts의 예제를 통해 다중 필드에서 다중 파일을 업로드 해보겠습니다.

import {
  Controller,
  Post,
  UploadedFiles,
  UseInterceptors,
} from '@nestjs/common';
import {
  FileFieldsInterceptor,
} from '@nestjs/platform-express';

@Controller()
export class AppController {
  @Post('/multiple')
  @UseInterceptors(
    FileFieldsInterceptor([{ name: 'first' }, { name: 'second' }]),
  )
  uploadMultipleFiles(
    @UploadedFiles() files: { [x: string]: Express.Multer.File[] },
  ) {
    const { first, second } = files;
    const list = [...first, ...second];
    return list.map(({ fieldname, originalname }) => ({
      fieldname,
      originalname,
    }));
  }
}

Postman을 통해 테스트를 해보겠습니다.

파일 2개를 각각 다른 필드에 나누어 업로드 해보겠습니다. 

 

필드를 나누지 않은 다중 파일 업로드

한개 이상의 필드에 파일을 포함하고 있지만, 필드 구분이 필요하지 않을 경우 AnyFilesInterceptor@UploadedFiles 데코레이터로  Express.Multer.File 형식의 다중 배열을 받아올 수 있습니다.

AnyFilesInterceptoroptions라는 하나의 파라미터를 입력할 수 있습니다.

 

똑같이 app.controller.ts를 예로 들어보겠습니다. 필드를 나누지 않고 다중 파일 업로드를 진행하는 예제입니다.

import { Controller, Post, UploadedFiles, UseInterceptors } from '@nestjs/common';
import { AnyFilesInterceptor } from '@nestjs/platform-express';

@Controller()
export class AppController {

  @Post('/multiple')
  @UseInterceptors(AnyFilesInterceptor())
  uploadMultipleFiles(@UploadedFiles() files: Express.Multer.File[]) {
    return files.map(({ fieldname, originalname }) => ({ fieldname, originalname }));
  }

}

 

Postman으로 요청을 보내보면 해당 파일들이 업로드 되어 배열 형식으로 지정한 양식에 알맞게 반환된 것을 확인할 수 있습니다.

 

multer의 기본 값 설정

각 기능에는 MulterOption을 지정해줄 수 있습니다.

하지만 매번 수동으로 구성하는건 번거롭기 때문에

Nest는 이런 반복 작업을 크게 줄일 수 있는 기본 값 설정 방법을 제공해주고 있습니다.

MulterModule을 가져와 register 메서드를 호출하기만 하면 되는데요.

이때 메서드는 MulterOption을 파라미터로 받습니다.

 

아래는 app.module.ts의 예제입니다. 업로드 하고자 하는 파일이 upload라는 폴더에 저장되게끔 하려면 register 안에 dest 속성을 추가해주어 ./upload에 저장될 수 있게끔 처리할 수 있습니다.

import { Module } from '@nestjs/common';
import { MulterModule } from '@nestjs/platform-express';
import { AppController } from './app.controller';
import { AppService } from './app.service';

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

 

파일 저장 해보기

multer의 기본 값 설정과 필드를 나누지 않은 다중 파일 업로드의 규칙에 따라 실험을 하나 해보겠습니다.

Postman을 통해 파일 두개를 업로드 한 후 upload 폴더에 정말 업로드가 되는지에 대한 실험이며,
결과는 아래와 같습니다.

어라, 뭔가이상하지 않나요? 왜 처음 지정했던 파일 이름과 딴판인 이름이 되어 서버에 저장되었을까요?

이는 multer가 파일 이름이 충돌할까봐 이를 피하기 위해 랜덤으로 이미지 이름이 설정된 것입니다.

 

파일 이름을 원래 파일 이름으로 수정하면 (확장자만 바꾸어도 됩니다.) 원래 파일들이 보입니다.

하지만 이는 그렇게 좋은 해결방법은 아닙니다. 개발자들은 반복 작업을 극도로 싫어하기 때문에..

multer가 제공하는 diskStorage를 사용한다면 파일 이름에 대한 문제를 해결할 수 있습니다. 

 

diskStorge는 하나의 함수로 제작되었으며 destination 메서드를 지정해줌으로써 해당 파일의 저장 위치를 설정해줄 수 있고 filename으로 파일의 이름을 설정할 수 있습니다. 이 두개의 속성 값은 모두 함수이며 이 함수들을 통해 탄력적으로 처리할 수 있습니다. 매 상황은 반드시 다를 수 밖에 없으니까요.

 

그러면 하나의 Helper Class를 선언해볼까요?
src 폴더 안에 core/helpers 폴더를 생성한 후 multer.helper.ts라는 파일을 하나 만들어줍시다.

 

이 두개의 함수에는 특정한 파라미터가 들어가기 때문에 메서드에는 파라미터의 필수값인 Request, Express/Multer.File(error: Error | null, destination: string)=> void의 Callback 함수를 선언하여 해당 Callback으로 multer에게 결과를 반환할 수 있도록 해봅시다.

import { Request } from 'express';
import { join } from 'path';

export class MulterHelper {

  public static destination(
    request: Request,
    file: Express.Multer.File,
    callback: (error: Error | null, destination: string) => void
  ): void {
    callback(null, join(__dirname, '../../../upload/'));
  }

  public static filenameHandler(
    request: Request,
    file: Express.Multer.File,
    callback: (error: Error | null, destination: string) => void
  ): void {
    const { originalname } = file;
    const timestamp = new Date().toISOString();
    callback(null, `${timestamp}-${originalname}`);
  }

}

이어서 이 두개의 함수를 적용해봅시다.

app.module.ts를 수정해서 register 객체 파라미터 안에 deststorage로 바꿔주고 destinationfilename을 아래와 같이 설정해줍니다.

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

import { diskStorage } from 'multer';

import { MulterHelper } from './core/helpers/multer.helper';

import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [
    MulterModule.register({
      storage: diskStorage({
        destination: MulterHelper.destination,
        filename: MulterHelper.filenameHandler
      })
    })
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

postman을 통해 2개의 파일을 업로드하면 방금 설정했던 upload 폴더 아래에 업로드된 시간과 해당 파일 이름으로 파일 이름이 설정되는 모습을 확인할 수 있습니다.

 

마치며

multer는 파일 업로드 기능을 간단하게 구현할 수 있도록 만들어진 하나의 Middleware입니다.

Nest는 multer를 더욱 간단하게 사용할 수 있도록 Nest의 설계 원칙과 일치하도록 설계되었습니다.

multer는 강력하고 훌륭한 패키지 입니다.

 

오늘 배운것들을 간단하게 요약하고 오늘의 포스팅을 마치겠습니다.

 

1. Nest는 multer를 파일 업로드의 기초 모듈로써 사용하고 있습니다.

2. multer는 multipart/form-data의 양식만을 허용하고 있습니다.

3. 단일 파일 업로드를 원한다면 FileInterceptor@UploadedFile 데코레이터를 통해 업로드할 수 있습니다.

4. 단일 필드에 다중 파일 업로드를 원한다면 FileFieldInterceptor@UploadedFiles 데코레이터를 통해 업로드 할 수 있습니다.

5. 다중 필드에 다중 파일 업로드를 원한다면 FileFieldsInterceptor@UploadedFiles 데코레이터를 통해 업로드 할 수 있습니다.

6. 필드를 나누지 않은 다중 파일 업로드를 원한다면 AnyFilesInterceptor@UploadedFiles 데코레이터를 통해 업로드 할 수 있습니다.

7. MulterModule.register()를 통해 multer의 기본 값을 설정할 수 있습니다.

8. storage 속성과 diskStorage를 통해 파일 저장을 구현할 수 있습니다.