package 설치
npm i -D @types/multer
FileInterceptor()
는 두개의 인자를 가진다.
fieldName
multipart/form-data
에서 name
에 해당하는 값options
: (option) MulterOptions
타입의 객체 [참조]import { Controller, Post, UploadedFile, UseInterceptors} from '@nestjs/common';
import { FileService } from './file.service';
import { FileInterceptor } from '@nestjs/platform-express';
@Controller('file')
export class FileController {
constructor(private readonly fileService: FileService) {}
@Post('upload')
@UseInterceptors(FileInterceptor('file'))
upload(@UploadedFile() file: Express.Multer.File) {
console.log(file);
}
}
Option을 생략하면 파일은 디스크가 아니라 메모리에 저장되며 기본적으로 Multer
는 이름의 중복을 방지하기 위해 내부적으로 임의로 이름을 생성하며 확장자는 붙어있지 않게 된다.
dest
: 파일 저장 위치 설정. 지정한 path에 디렉토리가 존재하지 않으면 자동적으로 생성되고 확장자가 없는 상태에서 Multer
내부에서 임의로 생성한 이름으로 저장된다.storage
: dest
보다 세밀하게 upload를 제어하고 싶을때 사용. DiskStorage
, MemoryStorage
를 제공DiskStorage
기준로 설명. destination
과 filename
을 설정할 수 있다.destination
: 파일 저장 위치 설정. 문자열이나 함수로 설정할 수 있다. 문자열로 설정할 경우 해당 디렉토리가 존재 하지 않으면 자동적으로 생성된다. 문자열이나 함수를 전달하지 않으면 기본적으로 os.tmpdir()
로 설정된다.filename
: 업로드한 파일의 이름을 설정하는 함수. 설정하지않으면 Multer
는 확장자가 없는 32자의 난수 16진수 문자열을 생성한다.fileFilter
: 업로드할 파일을 필터링하는 함수. 원하는 파일 형식만 허용하거나 거부할 수 있다.limits
: 업로드할 파일의 크기 및 파일 개수 제한
Key | Description | Default |
---|---|---|
fieldNameSize | 최대 필드 이름 크기 | 100 bytes |
fieldSize | 최대 필드 값 크기(바이트) | 1MB |
fields | 비파일 필드의 최대 개수 | Infinity |
fileSize | multipart forms의 최대 파일 크기(바이트) | Infinity |
files | multipart forms의 최대 파일 필드의 수 | Infinity |
parts | multipart/form-data의 파일과 필드의 개수 | Infinity |
headerPairs | multipart form에서 파싱할 헤더의 key-value 쌍의 최대 개수 | 2000 |
preservePath
: 파일의 경로를 보존할지 여부 설정. 기본값은 false이며, true로 설정하면 클라이언트가 제공한 파일 경로를 그대로 사용한다.import { Controller, Post, UploadedFile, UseInterceptors } from '@nestjs/common';
import { FileService } from './file.service';
import { FileInterceptor } from '@nestjs/platform-express';
import { diskStorage } from 'multer';
import { extname } from 'path';
@Controller('file')
export class FileController {
constructor(private readonly fileService: FileService) {}
@Post('upload')
@UseInterceptors(
FileInterceptor('image', {
storage: diskStorage({
destination: './uploads',
filename(_, file, cb): void {
const randomName = Array(32)
.fill(null)
.map(() => Math.round(Math.random() * 16).toString(16))
.join('');
return cb(null, `${randomName}${extname(file.originalname)}`);
},
}),
fileFilter(req, file, callback) {
if (!file.originalname.match(/\.(jpg|jpeg|png)$/)) {
return callback(new Error('Only .jpg, .jpeg, .png format allowed!'), false);
}
callback(null, true);
},
limits: {
fileSize: 1024 * 1024 * 5,
},
}),
)
upload(@UploadedFile() files: Express.Multer.File) {
console.log(files);
}
}
upload-interceoptor.ts
import { Injectable, NestInterceptor } from '@nestjs/common';
import { CallHandler, ExecutionContext, RequestHandler } from '@nestjs/common/interfaces';
import * as multer from 'multer';
import { diskStorage } from 'multer';
import { extname } from 'path';
import { Observable } from 'rxjs';
@Injectable()
export class UploadInterceptor implements NestInterceptor {
private upload: RequestHandler;
constructor() {
this.upload = multer({
storage: diskStorage({
destination: './uploads',
filename(_, file, cb): void {
const randomName = Array(32)
.fill(null)
.map(() => Math.round(Math.random() * 16).toString(16))
.join('');
return cb(null, `${randomName}${extname(file.originalname)}`);
},
}),
fileFilter(req, file, callback) {
if (!file.originalname.match(/\.(jpg|jpeg|png)$/)) {
return callback(new Error('Only .jpg, .jpeg, .png format allowed!'));
}
callback(null, true);
},
limits: {
fileSize: 1024 * 1024 * 5,
},
}).single('image');
}
intercept(context: ExecutionContext, next: CallHandler<any>): Observable<any> | Promise<Observable<any>> {
const req = context.switchToHttp().getRequest();
return new Observable((observer) => {
this.upload(req, null, async (err: any) => {
if (err) observer.error(err);
else next.handle().subscribe(observer);
});
});
}
}
앞선 컨트롤러 단에서 FileInterceptor를 사용하였을때는 해당 인터셉터들 사용하여 multer
함수를 따로 만들어 줄 필요이 제공받을 수 있는 반면 커스텀 인터셉터의 경우 constructor
내부에 multer
함수를 직접 정의한다.
file.controller.ts
import { Controller, Post, UploadedFile, UseInterceptors } from '@nestjs/common';
import { FileService } from './file.service';
import { UploadInterceptor } from 'src/interceptor/upload-interceptor';
@Controller('file')
export class FileController {
constructor(private readonly fileService: FileService) {}
@Post('upload')
@UseInterceptors(UploadInterceptor)
upload(@UploadedFile() files: Express.Multer.File) {
console.log(files);
}
}
ParseFilePipeBuilder
클래스를 사용하여 아래와 같이 각 유효성 검사기의 수동 인스턴스화를 피하고 옵션을 직접 전달할 수 있다.@Post('upload')
@UseInterceptors(FileInterceptor('file'))
upload(
@UploadedFile(
new ParseFilePipeBuilder()
.addFileTypeValidator({ fileType: /(mp4|png)$/ })
.addMaxSizeValidator({ maxSize: 1000 * 1024 * 1024 })
.build({ errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY }),
)
file: Express.Multer.File,
) {
console.log(file);
}
addFileTypeValidator()
의 fileType
은 문자열 또는 정규식 타입으로 설정addMaxSizeValidator()
의 maxSize
와 message
설정maxSize
: 바이트 기준message
: 오류 발생 메시지 설정build
의 errorHttpStatusCode
는 오류 코드 설정FilesInterceptor()
는 세개의 인자를 가진다.
fieldName
multipart/form-data
에서 name
에 해당하는 값maxCount
: (option) 최대 파일 수를 설정options
: (option) MulterOptions
타입의 객체 [참조]FilesInterceptor()
를 사용하는 경우 @UploadedFiles()
데코레이터를 사용하여 request에서 파일을 추출한다.
import {
Controller,
Post,
UseInterceptors,
UploadedFiles,
} from '@nestjs/common';
import { FileService } from './file.service';
import { FilesInterceptor } from '@nestjs/platform-express';
@Controller('file')
export class FileController {
constructor(private readonly fileService: FileService) {}
@Post('upload')
@UseInterceptors(FilesInterceptor('files'))
upload(
@UploadedFiles()
files: Array<Express.Multer.File>,
) {
console.log(files);
}
}
FileFieldsInterceptor()
데코레이터 이용하고, 이 데코레이터는 2개의 인자를 받는다.uploadedFields
: 객체의 배열로, 각 객체는 2개의 속성을 설정할 수 있다.name
속성을 통해 필드 이름을 문자열 값으로 설정maxCount
: (option) 최대 파일 수를 설정options
: (option) MulterOptions
타입의 객체 [참조]import {
Controller,
Post,
UseInterceptors,
UploadedFiles,
} from '@nestjs/common';
import { FileService } from './file.service';
import { FileFieldsInterceptor } from '@nestjs/platform-express';
@Controller('file')
export class FileController {
constructor(private readonly fileService: FileService) {}
@Post('upload')
@UseInterceptors(
FileFieldsInterceptor([
{ name: 'image', maxCount: 1 },
{ name: 'video', maxCount: 1 },
]),
)
upload(
@UploadedFiles()
files: {
image?: Express.Multer.File[];
video?: Express.Multer.File[];
},
) {
console.log(files);
}
}
file.controller.ts
import { Body, Controller, Post, UploadedFile, UseInterceptors } from '@nestjs/common';
import { FileService } from './file.service';
import { UploadInterceptor } from 'src/interceptor/upload-interceptor';
import { CreateFileDto } from './dto/create-file.dto';
@Controller('file')
export class FileController {
constructor(private readonly fileService: FileService) {}
@Post('upload')
@UseInterceptors(UploadInterceptor)
upload(@UploadedFile() files: Express.Multer.File, @Body() createFileDto: CreateFileDto) {
console.log(files);
console.log(createFileDto);
}
}
create-file.dto.ts
export class CreateFileDto {
title: string;
content: string;
image: any;
}
console.log
{
fieldname: 'image',
originalname: '테스트 이미지.png',
encoding: '7bit',
mimetype: 'image/png',
destination: './uploads',
filename: '0fc031767a579eee74fce9d0d49e161f.png',
path: 'uploads/0fc031767a579eee74fce9d0d49e161f.png',
size: 20751
}
[Object: null prototype] { title: '제목이요!', content: '컨텐츠' }
프로트단에서는 formdata.append()
를 이용하여 key-value 형태로 데이터를 추가하면 된다.
Documentation | NestJS - A progressive Node.js framework
GitHub - expressjs/multer: Node.js middleware for handling multipart/form-data
.
[NestJS] File-upload & Use of Interceptor (feat. nest를 바라보는 시각..)