Express router with middleware error handling - express

I have a question about error handling with middleware, specifically multer. I have this route:
router.post('/', saveFile, (req, res, next) => {
//rest of code
})
Then I have saveFile middleware:
const multer = require('multer')
const storage = multer.diskStorage({
destination: (req, res, cb) => {
cb(null, './uploads/')
},
filename: (req, res, cb) => {
cb(null, new Date().getTime() + '.jpg')
}
})
const fileFilter = (req, file, cb) => {
if (file.mimetype === 'image/jpeg') cb(null, true)
cb(null, false)
}
const upload = multer({
storage: storage,
limits: {
fileSize: 1024 * 1024 * 3 // up to 3 megabytes
},
fileFilter: fileFilter
})
const saveFile = upload.single('file')
module.exports.saveFile = saveAudio
The issue I have is that when I upload a file with a field name other than file, I get an error MulterError: Unexpected field. I want to catch this error somehow. But I don't even know where to do that. How do I do that?

The answer was pretty simple, yet nobody answered.
in the app.js where express is setup, you can make a middleware for handling errors
app.use((error, req, res, next) => errorHandlers(error, req, res, next))
And put it at last.
and then ErrorHandlers.js:
module.exports = function(error, req, res, next) {
if (error.name === 'MulterError') {
// handle error here
} else {
next()
}
}

Related

How do I make express middleware in class?

I currently use multer middleware like below
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, "public");
},
filename: function (req, file, cb) {
cb(null, req.params.id + "_" + file.originalname);
},
});
export const multerUploadSingle = (req: Request, res: Response, next: NextFunction) => {
const upload = multer({ storage: storage }).single("file");
upload(req, res, (error: unknown) => {
if (error instanceof multer.MulterError) {
const message = `file upload fail: ${error.message}`;
next(new HttpException(message, HttpStatus.BadRequest));
} else if (error instanceof Error) {
const message = `file upload fail: ${error.message}`;
next(new HttpException(message, HttpStatus.InternalServerError));
} else {
// upload success
next();
}
});
}
and use in router like this
FileRouter.post("/upload/:id", multerUploadSingle, (req, res) => {...});
However, I felt I want to refactor this middleware in class, and rewrote the code like this,
export class Multer {
private readonly storage: multer.StorageEngine;
constructor() {
this.storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, "public");
},
filename: function (req, file, cb) {
cb(null, req.params.id + "_" + file.originalname);
},
});
}
uploadSingle(req: Request, res: Response, next: NextFunction) {
const upload = multer({ storage: this.storage }).single("file");
upload(req, res, (error: unknown) => {
if (error instanceof multer.MulterError) {
const message = `file upload fail: ${error.message}`;
next(new HttpException(message, HttpStatus.BadRequest));
} else if (error instanceof Error) {
const message = `file upload fail: ${error.message}`;
next(new HttpException(message, HttpStatus.InternalServerError));
} else {
// upload success
next();
}
});
}
}
const multer = new Multer();
FileRouter.post("/upload/:id", multer.uploadSingle, (req, res) => {...});
With my short knowledge, I think both case should have the same result, but the latter case which uses class made middleware doesn't work at all. It's seems method "uploadSingle" is never called, thus multer not uploading the file.
Did I make any mistake with class usage? or is it just express can only use function defined middleware?
Your code should follow the MVC pattern.
You can do stuff like this:
routerFile.js
const upload = require("../../configs/multer");
const postController = require("../../controllers/postController");
const multiUploadEvent = upload.fields([
{ name: "images", maxCount: 2 },
{ name: "video", maxCount: 2 }
]);
router.post("/add-event-post", multiUploadEvent, postController.addEventPost);
module.exports = router;
multer.js
const multer = require('multer');
const multerFilter = (req, file, cb) => {
console.log("Mime type :", file.mimetype.split('/')[0]);
if (file.mimetype.split('/')[0] === 'image' || file.mimetype.split('/')[0] === 'video' || file.mimetype.split('/')[0] === 'audio') {
cb(null, true);
} else {
cb(new Error('Please upload img, audio, or video file only.'), false);
}
};
const storage = multer.memoryStorage();
const upload = multer({
storage: storage,
fileFilter: multerFilter,
limits: {
fileSize: , 50 * 1024 * 1024// 50 Mb
},
});
module.exports = upload;
postController.js
const addEventPost = async (request, response) => {
try {
let { title, ..... } = request.body;
const images = request.files.images;
const video = request.files.video;
console.log(title);
console.log(images);
console.log(videos);
//upload to services likes aws and save to database
.
.
.
return response
.status(200)
.json({
message: "Event post added successfully"
});
} catch (error) {
console.log(error);
response.status(500).json({
error: "Something went wrong",
});
}
}

express crashes if req.file is undefined

when making a post request to an express api without an attached file in the request, the api crashes and provides the TypeError: Cannot read properties of undefined (reading 'filename') error. However i would like to make it so the api does not crash when a post request is made without an attached image. any ideas ?
express code :
const storage = multer.diskStorage({
destination: (req, res, cb) => {
cb(null, dir)
},
filename: (req, file, cb) => {
cb(null, Date.now() + file.originalname)
}
})
const upload = multer({
storage: storage
})
router.get('/', async (req, res) => {
try {
const members = await Member.find();
res.json(members);
} catch (err) {
res.status(500).json({ message: err.message });
}
})
router.get('/:id', getMember, async (req, res) => {
res.json(res.member)
})
router.post('/', upload.single('image'), async (req, res) =>{
const member = new Member({
name: req.body.name,
occupation: req.body.occupation,
bio: req.body.bio,
join: req.body.join,
image: req.file.filename
})
try {
const newMember = await member.save()
res.status(201).json(newMember)
} catch (err) {
res.status(400).json({ message: err.message });
}
})
nextjs code to actually send the file:
const submitHandler = (e) => {
e.preventDefault()
const formDatas = new FormData()
formDatas.append('name', name)
formDatas.append('occupation', occupation)
formDatas.append('bio', paragraph)
formDatas.append('join', date)
formDatas.append('image', img)
console.log(formDatas)
axios
.post(api + '/members', formDatas)
.then(res => console.log(res))
.catch(err => console.log(err))
}
Error occurred because you're trying to access object of undefined variable req.file;
You can make changes according to your need
1 If you don't want to accept request without any file
router.post('/', upload.single('image'), async (req, res) => {
if (!req.file) { //or you can check if(req.file===undefiend)
return res.status(400).json({ message: 'Please attach a file' });
}
const member = new Member({
name: req.body.name,
occupation: req.body.occupation,
bio: req.body.bio,
join: req.body.join,
image: req.file.filename
})
try {
const newMember = await member.save()
res.status(201).json(newMember)
} catch (err) {
res.status(400).json({ message: err.message });
}
})
2 If you want to store null/empty string (in case of no file upload)
router.post('/', upload.single('image'), async (req, res) => {
const member = new Member({
name: req.body.name,
occupation: req.body.occupation,
bio: req.body.bio,
join: req.body.join,
image: req.file!==undefined ? req.file.filename : null
})
try {
const newMember = await member.save()
res.status(201).json(newMember)
} catch (err) {
res.status(400).json({ message: err.message });
}
})
Change as shown below
const member = new Member({
name: req.body.name,
occupation: req.body.occupation,
bio: req.body.bio,
join: req.body.join,
image: req.file.filename ? req.file.filename : ""
})
This should stop crashing but until I see the whole code wont know how you are handling error

node axios as middleware

I am trying to construct a middleware and then use it within the app.get route.
I know it's looks very "pioneer" but i am learning.... How can io get it to work?
const BooksMiddle = async (req, res, next) => {
axios
.get(`https://www.googleapis.com/books/v1/volumes/? q=${term}&keyes&key=${process.env.GBOOKSKEY}`)
.then((result) => {
const data = result.data;
const books = data.items;
return books;
});
next();
}
module.exports = textMiddle;
app.get("/", textMiddle, (req, res, next) => {
res.render('index');
});
If the point of this middleware is to get some book data and make that available for your template rendering, then you can put that data into res.locals where templates called from res.render() will automatically look for data:
const bookMiddle = async (req, res, next) => {
axios
.get(`https://www.googleapis.com/books/v1/volumes/?q=${term}&keyes&key=${process.env.GBOOKSKEY}`)
.then((result) => {
res.locals.books = result.data.items;
next();
}).catch(next);
}
module.exports = bookMiddle;
And, then after you import bookMiddle, you can use it like this:
app.get("/", bookMiddle, (req, res, next) => {
res.render('index');
});
If you refer to the books data structure in your template, the template engine will look in res.locals.books for that data (where the middleware puts the data).

Change multer destination folder based on the request

I am using express Multer middleware to save images in my application, I am using the same middleware to save the users, posts, and products images, the problem is all the images saved to the same directory "./public/uploads/", and what I want is to save each request to a specific folder,
For example:
The posts images to "./public/uploads/posts"
The products images to "./public/uploads/products"
I couldn't find a way to pass a variable from the routes to the middleware to change the destination dynamically
Could you please assist with this?
thanks in advance.
Here is my code:
Multer middleware file (multer.js)
const multer = require("multer");
const fs = require("fs");
let configDIR = "`./public/uploads/";
let storage = multer.diskStorage({
destination: (req, file, cb) => {
console.log('req.query.name', req.query.name)
let DIR = configDIR;
if (!fs.existsSync(DIR)) {
fs.mkdirSync(DIR, { recursive: true });
}
cb(null, DIR);
},
filename: (req, file, cb) => {
const fileName = "overDress" + Date.now() + "" +
file.originalname.toLowerCase().split(' ').join('-');
cb(null, fileName)
},
});
const upload =
multer({
storage: storage,
limits: {
fileSize: 1024 * 1024 //up to 1 megabytes per file.
},
}).single("image");
module.exports = {
upload,
};
One of my routes, the post route:
const controller = require('../controllers/post.controller');
import { Router } from 'express';
const router = Router();
import {upload} from '../middleware/multer'
let ImageFolder = 'posts'
router
.post('/',
(req, res, next) => {
upload(req, res, (err) => {
if (err)
return res.send({status:false, message: 'Invalid Image', error: err })
console.log('File Saved with no errors')
next()
}
)
export default router;

Express file upload with multer, but not as middleware

I'd like to use multer in my express route block, i.e. not as middleware. The multer config I have works as middleware, but I want to have a couple of checks before calling multer.
So I've tried this, to no avail:
/*
* Upload a file
*/
const MediaPath = "/var/tmp";
var multer = require('multer'); // Multer is for file uploading
var storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, MediaPath + '/' + req.params.organisation + '/' + req.params.table + '/');
},
filename: function (req, file, cb) {
// TODO - remove the time and timezone from ISO string, resolve the correct filename, create thumbnail,
var date = new Date();
cb(null, req.params.id + '.' + date.toISOString());
}
})
//var upload = multer({ storage: storage });
var upload = multer({ storage: storage }).single('file');
router.post('/file/:organisation/:table/:id', function (req, res, next){
db.resolveTableName( req )
.then( table => {
auth.authMethodTable( req )
.then( function() {
console.log('Uploading a file: ');
upload(req, res, function( err ) {
if( err ) {
console.log('Upload error' );
res.status(500).json( err );
}
console.log('Upload success' );
res.status(200).json("success");
});
})
.catch( function( error ) {
res.status(401).json('Unauthorized');
});
})
.catch( function(e) {
res.status(400).json('Bad request');
});
});
Funnily I get no error, so 200 is returned, but I get no uploaded file.
I took that pattern from here: https://www.ibm.com/developerworks/community/blogs/a509d54d-d354-451f-a0dd-89a2e717c10b/entry/How_to_upload_a_file_using_Node_js_Express_and_Multer?lang=en
Any ideas?
Fixed it by moving my controls to a middleware to call before multer, so I can use multer as middleware (inspired by this comment Nodejs Express 4 Multer | Stop file upload if user not authorized):
var preUpload = function( req, res, next ) {
db.resolveTableName( req )
.then( table => {
auth.authMethodTable( req )
.then( function() {
next();
})
.catch( function( error ) {
res.status(401).json('Unauthorized');
});
})
.catch( function(e) {
res.status(400).json('Bad request');
});
};
router.post('/file/:organisation/:table/:id', preUpload, upload.single('file'), function (req, res, next){
console.log(req.body, 'Body');
console.log(req.file, 'files');
res.end();
});