Today I'm gonna implement a fastify-multer upload image for NestJS using FastifyAdapter(),
Look! is it possible?
In Document Official Page
WARNING
Multer cannot process data which is not in the supported multipart format (multipart/form-data). Also, note that this package is not compatible with the FastifyAdapter.
Create Project and install package
1- nest new fastify-file-upload
2- yarn add @nestjs/platform-fastify @nestjs/swagger fastify-multer fastify-swagger fastify-helmet
main.ts
import { NestFactory } from '@nestjs/core';
import {
FastifyAdapter,
NestFastifyApplication,
} from '@nestjs/platform-fastify';
import { AppModule } from './app.module';
import { contentParser } from 'fastify-multer';
import 'reflect-metadata';
import { join } from 'path';
import helmet from 'fastify-helmet';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
const swaggerDocument = new DocumentBuilder()
.setTitle('API')
.setDescription('API')
.setVersion('1.0')
.addTag('API')
.build();
async function bootstrap() {
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
new FastifyAdapter(),
);
await app.listen(3000);
app.register(helmet, {
contentSecurityPolicy: {
directives: {
defaultSrc: [`'self'`],
styleSrc: [`'self'`, `'unsafe-inline'`],
imgSrc: [`'self'`, 'data:', 'validator.swagger.io'],
scriptSrc: [`'self'`, `https: 'unsafe-inline'`],
},
},
});
app.register(contentParser);
app.useStaticAssets({ root: join(__dirname, '../../fastify-file-upload') });
SwaggerModule.setup(
'api',
app,
SwaggerModule.createDocument(app, swaggerDocument),
);
console.log(`APP IS RUNNING ON PORT ${await app.getUrl()}`);
}
bootstrap();
we need to set up helmet for fastify because FastifyAdapter is not compatible with Swagger and contentParser for upload file
and register static file when we want to preview image in browser
app.useStaticAssets({ root: join(__dirname, '../../fastify-file-upload') });
Note fastify-file-upload
is your folder project
import { contentParser } from 'fastify-multer';
import helmet from 'fastify-helmet';
now we start code
fastify-file-interceptor.ts single file
import {
CallHandler,
ExecutionContext,
Inject,
mixin,
NestInterceptor,
Optional,
Type,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import FastifyMulter from 'fastify-multer';
import { Options, Multer } from 'multer';
type MulterInstance = any;
export function FastifyFileInterceptor(
fieldName: string,
localOptions: Options,
): Type<NestInterceptor> {
class MixinInterceptor implements NestInterceptor {
protected multer: MulterInstance;
constructor(
@Optional()
@Inject('MULTER_MODULE_OPTIONS')
options: Multer,
) {
this.multer = (FastifyMulter as any)({ ...options, ...localOptions });
}
async intercept(
context: ExecutionContext,
next: CallHandler,
): Promise<Observable<any>> {
const ctx = context.switchToHttp();
await new Promise<void>((resolve, reject) =>
this.multer.single(fieldName)(
ctx.getRequest(),
ctx.getResponse(),
(error: any) => {
if (error) {
// const error = transformException(err);
return reject(error);
}
resolve();
},
),
);
return next.handle();
}
}
const Interceptor = mixin(MixinInterceptor);
return Interceptor as Type<NestInterceptor>;
}
fastify-files-interceptor.ts for multiple file
import {
CallHandler,
ExecutionContext,
Inject,
mixin,
NestInterceptor,
Optional,
Type,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import FastifyMulter from 'fastify-multer';
import { Options, Multer } from 'multer';
type MulterInstance = any;
export function FastifyFilesInterceptor(
fieldName: string,
maxCount?: number,
localOptions?: Options,
): Type<NestInterceptor> {
class MixinInterceptor implements NestInterceptor {
protected multer: MulterInstance;
constructor(
@Optional()
@Inject('MULTER_MODULE_OPTIONS')
options: Multer,
) {
this.multer = (FastifyMulter as any)({ ...options, ...localOptions });
}
async intercept(
context: ExecutionContext,
next: CallHandler,
): Promise<Observable<any>> {
const ctx = context.switchToHttp();
await new Promise<void>((resolve, reject) =>
this.multer.array(fieldName, maxCount)(
ctx.getRequest(),
ctx.getResponse(),
(error: any) => {
if (error) {
// const error = transformException(err);
return reject(error);
}
resolve();
},
),
);
return next.handle();
}
}
const Interceptor = mixin(MixinInterceptor);
return Interceptor as Type<NestInterceptor>;
}
Now go to app.controller.ts add following code
import {
Body,
Controller,
Get,
Post,
Req,
UploadedFile,
UploadedFiles,
UseInterceptors,
} from '@nestjs/common';
import { ApiConsumes, ApiTags } from '@nestjs/swagger';
import { Request } from 'express';
import { diskStorage } from 'multer';
import { AppService } from './app.service';
import { MultipleFileDto } from './dto/multiple-files-dto';
import { SingleFileDto } from './dto/single-file-dto';
import { FastifyFileInterceptor } from './interceptor/fastify-file-interceptor';
import { FastifyFilesInterceptor } from './interceptor/fastify-files-interceptor';
import { fileMapper, filesMapper } from './utils/file-mapper';
import { editFileName, imageFileFilter } from './utils/file-upload-util';
@Controller()
@ApiTags('Upload File ')
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
@ApiConsumes('multipart/form-data')
@Post('single-file')
@UseInterceptors(
FastifyFileInterceptor('photo_url', {
storage: diskStorage({
destination: './upload/single',
filename: editFileName,
}),
fileFilter: imageFileFilter,
}),
)
single(
@Req() req: Request,
@UploadedFile() file: Express.Multer.File,
@Body() body: SingleFileDto,
) {
return { ...body, photo_url: fileMapper({ file, req }) };
}
@ApiConsumes('multipart/form-data')
@Post('multiple-file')
@UseInterceptors(
FastifyFilesInterceptor('photo_url', 10, {
storage: diskStorage({
destination: './upload/single',
filename: editFileName,
}),
fileFilter: imageFileFilter,
}),
)
multiple(
@Req() req: Request,
@UploadedFiles() files: Express.Multer.File[],
@Body() body: MultipleFileDto,
) {
return { ...body, photo_url: filesMapper({ files, req }) };
}
}
single-file-dto.ts
import { ApiProperty } from "@nestjs/swagger";
// you can add validate using class-validator
export class SingleFileDto{
@ApiProperty({type:"string",format:"binary"})
photo_url:string
@ApiProperty({example:"Rom"})
username:string
@ApiProperty({example:"12345678"})
password:string
}
multiple-files-dto.ts
import { ApiProperty } from '@nestjs/swagger';
// you can add validate using class-validator
export class MultipleFileDto {
@ApiProperty({ type: Array, format: 'binary' })
photo_url: string[];
@ApiProperty({ example: 'Rom' })
username: string;
@ApiProperty({ example: '12345678' })
password: string;
}
Now create folder call utils
create file-upload-util.ts
import { Request } from 'express';
import { extname } from 'path';
export const editFileName = (
req: Request,
file: Express.Multer.File,
callback
) => {
const name = file.originalname.split('.')[0];
const fileExtName = extname(file.originalname);
const randomName = Array(4)
.fill(null)
.map(() => Math.round(Math.random() * 16).toString(16))
.join('');
callback(null, `${name}-${randomName}${fileExtName}`);
};
export const imageFileFilter = (
req: Request,
file: Express.Multer.File,
callback
) => {
if (!file.originalname.match(/\.(jpg|jpeg|png|gif)$/)) {
return callback(new Error('Only image files are allowed!'), false);
}
callback(null, true);
};
create file-mappter.ts
import { Request } from 'express';
interface FileMapper {
file: Express.Multer.File;
req: Request;
}
interface FilesMapper {
files: Express.Multer.File[];
req: Request;
}
export const fileMapper = ({ file, req }: FileMapper) => {
const image_url = `${req.protocol}://${req.headers.host}/${file.path}`;
return {
originalname: file.originalname,
filename: file.filename,
image_url,
};
};
export const filesMapper = ({ files, req }: FilesMapper) => {
return files.map((file) => {
const image_url = `${req.protocol}://${req.headers.host}/${file.path}`;
return {
originalname: file.originalname,
filename: file.filename,
image_url,
};
});
};
-Why define Interface Express for
@UploadFile() file:Express.Multer.File
and
@UploadFiles() files:Express.Multer.File[]
because the package we installed yarn add fastify-multer
is base on the package express multer
.
-file-upload-utils.ts have 2 function for editFilename and filterImage.
-file-mapper.ts for creating a new object.
-Now open chrome enter http://localhost:3000/api and try to upload have single file and multiple files.
Let's start upload our image on swagger ui
You copy the image_url paste in the browser to view image
-Package Fastify-Multer Repository on GitHub: https://github.com/fox1t/fastify-multer
-Repository Code: https://github.com/chanphiromsok/Fastify-Upload
Top comments (4)
How implement interceptor if client want to attache files with different keys
may you check this one npmjs.com/package/fastify-file-int...
how to implement files interceptors if client want to attache files with different keys in to one route handler
what a horrific amount of code we have to build out just to upload a file. there's gotta be a simpler way. not using fastify entirely, perhaps?