[NodeJS] Using Multer and Cls-Hooked together
Multer and Cls-hooked are two important modules in the NodeJS ecosystem. Multer,
Multer is a node.js middleware for handling
multipart/form-data
, which is primarily used for uploading files.
Cls-Hooked is an extension of CLS which used to preserve the context with async/await,
Continuation-local storage works like thread-local storage in threaded programming, but is based on chains of Node-style callbacks instead of threads.
Let’s consider a general use case of these modules in an Express application.
The following web app, exposes POST /upload
endpoint to upload a file. We use Multer middleware to read the file named userfile
in the request. Cls-hooked is used to generate a unique loggerId
for each request (We print it in the controller).
const express = require('express');
const multer = require('multer');
const createNamespace = require('cls-hooked').createNamespace;
const { v4: uuidv4 } = require('uuid');const namespace = createNamespace('EXPRESS_APP');const app = express();const upload = multer();// generate loggerId
app.use((req, res, next) => {
namespace.run(() => {
// loggerId is set in the context
namespace.set('loggerId', uuidv4());
next();
});
});app.post('/upload', upload.single('userfile') , async (req, res) => {
console.log('request received', namespace.get('loggerId'));
// do the processing with file
// const result = await processFile(req.file);
res.send('OK');
});app.listen(3000, () => {
console.log('App started');
});
Notes: uuid is used to generate a random UUID. Good oldconsole.log
is used here for logging, for sophisticated logging in your application, use a module like pino.
If you run this code what you’ll see in the console is,
request received undefined
Why?
When multer middleware is used, the CLS context is lost (https://github.com/expressjs/multer/issues/814). I wasted a good amount of time when I faced this issue not knowing what the real problem is.
How to Fix?
The way I fixed issue is why wrapping the Multer’s upload
with a Promise
Here’s the code. (Only the diff is given below)
...const upload = multer().single('userfile'); ...// Promise wrapping the Multer upload
const multerPromise = (req, res) => {
return new Promise((resolve, reject) => {
upload(req, res, (err) => {
if(!err) resolve();
reject(err);
});
});
};const uploadMiddleware = async (req, res, next) => {
try {
await multerPromise(req, res);
next();
} catch(e) {
next(e);
}
};app.post('/upload', uploadMiddleware , async (req, res) => { ...
If multerPromise
is resolved without any errors, req.file
will contain the file (Usual Multer behavior). Now you’ll see something like following in the Console.
request received 3e8d43b5-83fa-4c39-bb0e-5828fb79d197
Hope this will be useful to anyone struggling to get Multer and Cls-Hooked work together.