I use multer to manage uploaded file:
#Post('upload') #UseInterceptors(FilesInterceptor("images", 10, {
dest: "./uploads",
}))
uploadMultiple(#UploadedFiles() files) {
console.log(files, 'test');
}
I try to add a file extension to my uploaded files as:
#Post('upload') #UseInterceptors(FilesInterceptor("images", 10, {
dest: "./uploads",
filename: function (req, file, cb) {
cb(null, Date.now() + '.jpg') //Appending .jpg
}
}))
But when I do this I get an error:
TS2345: Argument of type '{ dest: string; filename: (req: any, file: any, cb: any) => void; }' is not assignable to parameter of type 'MulterOptions'. Object literal may only specify known properties, and 'filename' does not exist in type 'MulterOptions'
How to specify file extension to my uploaded files?
You can do it dynamically using the extname import from path using the diskStorage to get your filename extension.
import { extname } from 'path';
import { diskStorage } from 'multer';
export const exampleDiskStorage = diskStorage({
destination: './public/img/users',
filename: (req, file, cb) => {
return cb(null, `${Date.now()}${extname(file.originalname)}`);
}
});
In your module, you only need to import the diskStorage to MulterModule.register
import { MulterModule } from '#nestjs/platform-express';
#Module({
imports: [
MulterModule.register({
storage: exampleDiskStorage,
}),
],
});
Related
After uploading an image to S3 bucket, it looks strange most of the time, but the same images looks OK sometimes, so I'm not sure what's going on. It started happening from last week and before that it was working fine.
Here is a sample image that I uploaded, and after uploading, it appears as follows.
https://hub-uploads-stage.s3.us-east-2.amazonaws.com/gallery-documents/file-1675056885128-alphabet-gcf3617eaf_1920.jpg
Original Image
https://i.imgur.com/ChbHNn9.png
I'm using packages: multer, multer-s3 with node js 16.19.0
I'm using following dependencies
multer: ^1.4.2
multer-s3: ^2.10.0
aws-sdk: ^2.1002.0
file-type: ^16.5.3
Here is the code I used to upload files.
import AWS from "aws-sdk";
import multer from "multer";
import multerS3 from "multer-s3";
import { fromBuffer } from 'file-type';
import stream from 'stream';
AWS.config.update({
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
region: process.env.AWS_REGION,
signatureVersion: "s3v4",
});
const s3 = new AWS.S3();
export const basePath = {
profile: "profile-images",
post: "post-documents",
slider: "slider-images",
gallery: "gallery-documents",
icons: "icons",
posters: "product-posters",
chat: "chat"
}
const buckets = {
uploads: process.env.S3_BUCKET_UPLOADS || 'hub-uploads-stage'
}
const mimeTypes = {
image: [
"image/gif",
"image/jpg",
"image/jpeg",
"image/png",
"image/tiff",
"image/pipeg",
"image/svg+xml",
"image/bmp",
"image/x-xbitmap",
"image/x-icon"
]
}
const storage = (Bucket: any, uploadBasePath: any) => multerS3({
s3,
bucket: Bucket,
acl: 'public-read',
contentType: function (req: any, file: any, callback: any) {
file.stream.once('data', async function (firstChunk: any) {
var type = await fromBuffer(firstChunk)
var mime = (type === null ? 'application/octet-stream' : type?.mime)
var outStream = new stream.PassThrough()
outStream.write(firstChunk)
file.stream.pipe(outStream)
callback(null, mime, outStream)
})
},
metadata: function (req: any, file: any, callback: any) {
callback(null, { fieldName: file.originalname })
},
key: function (req: any, file: any, callback: any) {
let fileName = `${file.fieldname}-${Date.now()}-${file.originalname}`;
let filePath = uploadBasePath + '/' + fileName;
callback(null, filePath)
}
})
export const uploader = {
profile: multer({
storage: storage(buckets.uploads, basePath.profile),
fileFilter: function (req, file, callback) {
if (mimeTypes.image.includes(file.mimetype)) {
callback(null, true)
} else {
callback(null, false)
}
}
}),
post: multer({
storage: storage(buckets.uploads, basePath.post)
}),
slide: multer({
storage: storage(buckets.uploads, basePath.slider),
fileFilter: function (req, file, callback) {
if (mimeTypes.image.includes(file.mimetype)) {
callback(null, true)
} else {
callback(null, false)
}
}
}),
gallery: multer({
storage: storage(buckets.uploads, basePath.gallery),
fileFilter: function (req, file, callback) {
console.log("File in filter =>", file)
callback(null, true)
}
}),
icon: multer({
storage: storage(buckets.uploads, basePath.icons)
}),
productPoster: multer({
storage: storage(buckets.uploads, basePath.posters),
fileFilter: function (req, file, callback) {
if (mimeTypes.image.includes(file.mimetype)) {
callback(null, true)
} else {
callback(null, false)
}
}
}),
chat: multer({
storage: storage(buckets.uploads, basePath.chat)
})
};
Is there something i need to upgrade or change?
Thanks
I have Node.js app, served by express, with my frontend being made with React.js. My issue is i got to different fieldname for my images. Cover Photo and Avatar Photo. I'm having hard time to figure out how to loop into to different fieldname and get the path of the image. The result that i want is the backend will res.send(path of the image either avatar or cover or both of them).
// imageuploadroutes
import express from 'express';
const router = express.Router();
import multer from 'multer';
import path from 'path';
const storage = multer.diskStorage({
destination: function (req, file, cb) {
if (file.fieldname === 'coverPhoto') {
cb(null, 'public/images/cover');
} else {
cb(null, 'public/images/avatar');
}
},
filename: function (req, file, cb) {
cb(
null,
`${file.fieldname}-${Date.now()}${path.extname(file.originalname)}`
);
},
});
const upload = multer({
storage,
limits: {
fileSize: '2mb',
},
fileFilter: (req, file, cb) => {
if (
file.mimetype == 'image/png' ||
file.mimetype == 'image/jpg' ||
file.mimetype == 'image/jpeg'
) {
cb(null, true);
} else {
cb(null, false);
return cb(new Error('Only .png, .jpg and .jpeg format allowed!'));
}
},
});
router.post(
'/user/profile',
upload.fields([
{
name: 'coverPhoto',
maxCount: 1,
},
{
name: 'avatarPhoto',
maxCount: 1,
},
]),
function (req, res) {
var file = req.files[Object.keys(req.files)[0]];
console.log(file)
}
);
export default router;
Result
[
{
fieldname: 'avatarPhoto',
originalname: 'Screen Shot 2021-03-02 at 11.49.56 AM.png',
encoding: '7bit',
mimetype: 'image/png',
destination: 'public/images/avatar',
filename: 'avatarPhoto-1614704247624.png',
path: 'public/images/avatar/avatarPhoto-1614704247624.png',
size: 597941
}
]
but the problem is I can't get the .path
The file is an array, you should iterate over it:
var file = req.files[Object.keys(req.files)[0]];
console.log(file);
file.forEach(fileData => {
console.log(fileData.path);
})
I am trying to upload a file to my nest.js server but I am getting an error:
Error: Unsupported Media Type: multipart/form-data; boundary=--------------------------140603536005099484714904
I followed this documentation.
Angular 6 Code
public async Upload<TReponse>(file: File, path) {
try {
const formData = new FormData();
formData.append("file", file);
//>> function to upload and applying header as null
const result = await this.http.post(this.baseUrl + path, formData, { headers: null }).pipe(map((response: any) => response)).toPromise();
var response = result as ApiResponseObject<TReponse>;
return response;
}
catch (ex) {
var result = new ApiResponseObject<TReponse>()
result.message = ex.message;
result.isSuccess = false;
return result;
}
finally {
}
}
NestJs Code Module Code
import { Module, NestModule, MiddlewareConsumer, MulterModule } from '#nestjs/common';
import { UserService } from './services/user.service';
import { UserController } from './user.controller';
import { TypeOrmModule } from '#nestjs/typeorm';
import { User } from './dto/user.entity';
import * as multer from 'multer';
#Module({
imports: [TypeOrmModule.forFeature([User]),
MulterModule.register({
dest: '/public',
fileFilter: (req, file, cb) => {
let extension = (file.originalname.split('.').pop())
//Put here your custom validation for file extensións.
// To accept the file pass `true`, like so:
cb(null, true);
// To reject this file pass `false` or throw Exception, like so:
//cb(new HttpException ("File format is not valid", HttpStatus.BAD_REQUEST), false)
},
limits: {
fileSize: 2097152 //2 Megabytes
},
storage: multer.diskStorage({
destination(req, file, cb) {
cb(null, './public');
},
filename(req, file, cb) {
cb(null, "usman_" + file.originalname);
},
})
}),
],
providers: [UserService],
controllers: [UserController],
})
NestJs Code Component Code
#Post("upload")
#UseInterceptors(FileInterceptor('file'))
async upload(#UploadedFile() file, #Request() req) {
console.log(file);
console.log(req.files);
}
I assume you are using the FastifyAdapter instead of express.
In the documentation it says:
Multer will not process any form which is not multipart
(multipart/form-data). Besides, this package won't work with the
FastifyAdapter.
So if possible, switch to express. Otherwise, you have to use Fastify-Multipart until it is natively supported by nest.js.
I have a document that contains two fields are of input type="file" and I want to upload both of these on submit.
This post method giving me internal server error 500 on uploading two files but when I upload one file, it is OK.
router.post('/', mediaFiles.uploadSingle('icon_url'), mediaFiles.uploadSingle('background_url'),
async (req, res) => {
name: req.body.name,
icon_url: req.file.path.replace(/\\/g, "/"), // req.file['icon_url']
background_url: req.file.path.replace(/\\/g, "/") // req.file['background_url']
})
you can ignore this MediaFiles class because it provides traditional code to upload images with multer
import multer from "multer";
import path from "path";
class MediaFiles {
private storage = multer.diskStorage({
destination: 'uploads/',
filename: function (req, file, callback) {
callback(
null,
file.originalname.replace(/\.[^/.]+$/, "") + '-' + Date.now() + path.extname(file.originalname))
}
})
uploadSingle(fieldName?: string) {
try {
return multer({
storage: this.storage,
limits: { fileSize: 1024 * 1024 * 1 }, // 1MB = 1024 * 1024 * 1
fileFilter: function (req, file, callback) {
const fileTypes = /jpeg|jpg|png/;
const extName = fileTypes.test(path.extname(file.originalname).toLowerCase());
const mimeType = fileTypes.test(file.mimetype);
if (extName && mimeType) {
callback(null, true)
} else {
callback(new Error('Error: Images Only!'), null)
}
}
}).single(fieldName);
} catch (err) {
console.log(err.message)
}
}
}
export default new MediaFiles();
I don't think you could have two multer objects, I was having the same problem and here is what worked for me.
const storage = multer.diskStorage()
const mediaFiles = multer({
storage:storage })
.fields([{ name: 'icon_url', maxCount: 1 }, { name: 'background_url', maxCount: 1 } ]
router.post('/', mediaFiles, async (req, res) => {
console.log(req.files) // req.files is an array of files
}
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?
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
Using Multer options
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)
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)
}
Cleanest implementation using Multer options
Thank you #VictorIvens for the best answer out of the bunch.
However, I found the following problems in the code.
the import called FileInterceptor does not exist withing #nestjs/common package int the latest version of NestJS.
the code looks a bit too cluttered to my eyes.
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;
}
}
**
2021 Update
**
To do this now, you need to import FileInterceptor like this...
import { FileInterceptor } from '#nestjs/platform-express';
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,
};
}
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);
}
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));