Reputation: 2279
Since nestjs is an express app, it's possible to use any library to handle upload using nest, and since it provides Midlewares, it's also possible to use multer. My question is: What's the best way to handle file uploads using nestjs?
Upvotes: 12
Views: 96418
Reputation: 308
Use custom Decorator here an example
import { createParamDecorator, ExecutionContext, InternalServerErrorException } from '@nestjs/common';
import Busboy from 'busboy';
import { Request } from 'express';
export const FileUpload = createParamDecorator(
(data: unknown, ctx: ExecutionContext): Promise<Express.Multer.File> => {
const req = ctx.switchToHttp().getRequest<Request>();
return new Promise((resolve, reject) => {
const fileContent: any[] = [];
const bb = Busboy({ headers: req.headers as any });
let multerFile: Partial<Express.Multer.File> = {};
bb.on('file', (fieldname, file, info) => {
const { filename, encoding, mimeType } = info;
file.on('data', (data) => {
fileContent.push(data);
});
file.on('end', () => {
const buffer = Buffer.concat(fileContent);
// Simular el objeto Express.Multer.File
multerFile = {
fieldname,
originalname: filename,
encoding,
mimetype: mimeType,
buffer,
size: buffer.length,
destination: '',
filename,
path: '',
};
});
});
bb.on('finish', () => {
resolve(multerFile as Express.Multer.File);
});
bb.on('error', (error: any) => {
reject(new InternalServerErrorException('Failed to process file upload', error.message));
});
bb.end(req.body);
});
},
);
Upvotes: 0
Reputation: 2279
As informed by @Kamyl on issue https://github.com/nestjs/nest/issues/262, since v4.6.0
is possible to upload files using multer to nestjs using a common file interceptor.
import { ... , UseInterceptors, FileInterceptor, UploadedFile } from '@nestjs/common'
...
@UseInterceptors(FileInterceptor('file'))
async upload( @UploadedFile() file) {
console.log(file)
}
This way the variable file
will have a buffer
It's also needed the field name as the first param, then an array with Multer Options
import { ... , UseInterceptors, FileInterceptor, UploadedFile } from '@nestjs/common'
import { diskStorage } from 'multer'
import { extname } from 'path'
...
@UseInterceptors(FileInterceptor('file', {
storage: diskStorage({
destination: './uploads'
, filename: (req, file, cb) => {
// Generating a 32 random chars long string
const randomName = Array(32).fill(null).map(() => (Math.round(Math.random() * 16)).toString(16)).join('')
//Calling the callback passing the random name generated with the original extension name
cb(null, `${randomName}${extname(file.originalname)}`)
}
})
}))
async upload( @UploadedFile() file) {
console.log(file)
}
This way the variable file
will have a filename
, destination
and path
.
The destination
param from the diskStorage
can also be a function, with the parameters and expecting the callback the same as filename
. By passing a diskStorage
the file will be automatically saved to the destination informed with the filename given.
It's also possible to handle multiple files by using @UploadedFiles
and FilesInterceptor
(plural)
Upvotes: 40
Reputation: 9933
I have written the code to upload single and multiple files using NEST.JS:
app.controller.ts:
import { Controller, Post, UploadedFiles, UploadedFile, UseInterceptors } from '@nestjs/common';
import { FileInterceptor, FilesInterceptor } from '@nestjs/platform-express';
import { AppService } from './app.service';
import { Express } from 'express';
@Controller('api/portal/file')
export class AppController {
constructor(private appService: AppService) {}
@Post('/multiple')
@UseInterceptors(FilesInterceptor('files'))
async uploadFiles(@UploadedFiles() files: Array<Express.Multer.File>) {
const req = {
files,
prospectId: 1234,
};
return await this.appService.getUrls(req);
}
@Post('/single')
@UseInterceptors(FileInterceptor('file'))
async uploadFile(@UploadedFile() file: Express.Multer.File) {
const req = {
files: [file],
prospectId: 1234,
};
return await this.appService.getUrls(req);
}
}
app.service.ts:
import { Injectable } from '@nestjs/common';
import { FileDataReq, FileDataRes } from './dto/app.dto';
@Injectable()
export class AppService {
async getUrls(uploadData: FileDataReq): Promise<FileDataRes> {
const { prospectId } = uploadData;
const response = { urls: [], prospectId };
const { files } = uploadData;
for (const file of files) {
const { originalname } = file;
let url = `/${prospectId}/${new Date().getTime()}_${originalname.trim().split(' ').join('_')}`;
response.urls.push(url);
}
return response;
}
}
app.dto.ts:
export class FileDataReq {
files: any[];
prospectId: number;
}
export class FileDataRes {
urls: string[];
prospectId: number;
}
Postman:
Upvotes: 0
Reputation: 2932
A cleaner way would be to extract the configurations to a separate file and then call it inside the interceptor method
import { extname } from 'path';
import { existsSync, mkdirSync } from 'fs';
import { diskStorage } from 'multer';
import { v4 as uuid } from 'uuid';
import { HttpException, HttpStatus } from '@nestjs/common';
// Multer configuration
export const multerConfig = {
dest: process.env.UPLOAD_LOCATION,
};
// Multer upload options
export const multerOptions = {
// Enable file size limits
limits: {
fileSize: +process.env.MAX_FILE_SIZE,
},
// Check the mimetypes to allow for upload
fileFilter: (req: any, file: any, cb: any) => {
if (file.mimetype.match(/\/(jpg|jpeg|png|gif)$/)) {
// Allow storage of file
cb(null, true);
} else {
// Reject file
cb(new HttpException(`Unsupported file type ${extname(file.originalname)}`, HttpStatus.BAD_REQUEST), false);
}
},
// Storage properties
storage: diskStorage({
// Destination storage path details
destination: (req: any, file: any, cb: any) => {
const uploadPath = multerConfig.dest;
// Create folder if doesn't exist
if (!existsSync(uploadPath)) {
mkdirSync(uploadPath);
}
cb(null, uploadPath);
},
// File modification details
filename: (req: any, file: any, cb: any) => {
// Calling the callback passing the random name generated with the original extension name
cb(null, `${uuid()}${extname(file.originalname)}`);
},
}),
};
and then call it under the interceptor like so
import { ... , UseInterceptors, FileInterceptor, UploadedFile } from '@nestjs/common'
import { diskStorage } from 'multer'
import { extname } from 'path'
import { multerOptions } from 'src/config/multer.config';
...
@Post('/action/upload')
@UseInterceptors(FileInterceptor('file', multerOptions))
async upload( @UploadedFile() file) {
console.log(file)
}
Upvotes: 35
Reputation: 190
Create a helper.ts file that rename your file and contains path
export class Helper {
static customFileName(req, file, cb) {
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9);
let fileExtension = "";
if(file.mimetype.indexOf("jpeg") > -1){
fileExtension = "jpg"
}else if(file.mimetype.indexOf("png") > -1){
fileExtension = "png";
}
const originalName = file.originalname.split(".")[0];
cb(null, originalName + '-' + uniqueSuffix+"."+fileExtension);
}
static destinationPath(req, file, cb) {
cb(null, 'uploads/')
}
}
code for controller
import { Helper } from '../service/Helper';
import { diskStorage } from 'multer';
import {FileInterceptor} from '@nestjs/platform-express'
import {Controller, Post, Body, UseInterceptors, UploadedFile} from '@nestjs/common'
@Post('upload')
@UseInterceptors(
FileInterceptor('picture', {
storage: diskStorage({
destination: Helper.destinationPath,
filename: Helper.customFileName,
}),
}),
)
uploadFile(@UploadedFile() file: Express.Multer.File) {
console.log(file);
}
Upvotes: 0
Reputation: 670
Thank you @VictorIvens for the best answer out of the bunch.
However, I found the following problems in the code.
FileInterceptor
does not exist withing @nestjs/common
package int the latest version of NestJS.So, to simplify the things up, I have come up with the following solution.
storage.config.ts
export const storage = diskStorage({
destination: "./uploads",
filename: (req, file, callback) => {
callback(null, generateFilename(file));
}
});
function generateFilename(file) {
return `${Date.now()}.${extname(file.originalname)}`;
}
your-controller.controller.ts
import {
Controller,
Post,
UseInterceptors,
UploadedFile
} from "@nestjs/common";
import { FileInterceptor } from "@nestjs/platform-express";
import { diskStorage } from "multer";
import { extname } from "path";
import { storage } from "./storage.config"
@Controller()
export class YourController {
@Post("upload") // API path
@UseInterceptors(
FileInterceptor(
"file", // name of the field being passed
{ storage }
)
)
async upload(@UploadedFile() file) {
return file;
}
}
Upvotes: 7
Reputation: 12410
**
**
To do this now, you need to import FileInterceptor like this...
import { FileInterceptor } from '@nestjs/platform-express';
Upvotes: 2
Reputation: 139
If you are getting the data from the user via API call, you can save the data as buffer and access the content using adm-zip. Below is the controller method implementation in nest.js.
@Post("/blackBoardUpload")
@UseInterceptors(
FileInterceptor('image', {
storage: memoryStorage(),
fileFilter: zipFileFilter,
}),
)
async uploadedFile(@UploadedFile() file) {
console.log(file)
const response = {
originalname: file.originalname,
filename: file.filename,
};
var AdmZip = require('adm-zip');
var zip = new AdmZip(file.buffer);
var zipEntries = zip.getEntries();
console.log(zipEntries.length);
return {
status: HttpStatus.OK,
message: 'Received Zip file successfully!',
data: response,
};
}
Upvotes: 0
Reputation: 1247
A simple way is to use controllers. You need to define an upload controller and add it in your app.module, this is an example of what a controller should be (back-end):
@Controller()
export class Uploader {
@Post('sampleName')
@UseInterceptors(FileInterceptor('file'))
uploadFile(@UploadedFile() file) {
// file name selection
const path = `desired path`;
const writeStream = fs.createWriteStream(path);
writeStream.write(file.buffer);
writeStream.end();
return {
result: [res],
};
}
}
And call your controller by fetch in the front-end:
fetch('controller address', {
method: 'POST',
body: data,
})
.then((response) => response.json())
.then((success) => {
// What to do when succeed
});
})
.catch((error) => console.log('Error in uploading file: ', error));
Upvotes: -3