Uploading form fields and files at the same time with Multer (Node.js, Typescript)

Tamás Polgár
Developer rants
Published in
4 min readJul 29, 2020

Multer is a widely used middleware to handle file uploads and multipart/form-data stuff received by your Node.js server application. There are tons of documentation and tutorials to teach you how to use Multer to upload files to your application. It’s not that simple to figure out how to upload files and form data at the same time though!

The problem

As you probably know, form data can be submitted with various encodings, form-data or multipart/form-data being the oldest and most widely used. You can also use x-www-form-uuencoded, raw or json but neither of these can easily transfer a file.

But Express and its body-parser extension can’t parse multipart/form-data. ThePOST request will invoke the appropriate route, but req.body will be undefined. Only Multer can save us. (Well, not really, but this article is about Multer.)

The solution

Let’s say we’re trying to send some traditional form data and a file. It has to be multipart/form-data. For the sake of this simple demo let’s send only one form field and a test file from Postman. Let it be just one form field called whatever with the string value of 'nothing' and a file called file.

What is happening on server side? It’s all in App.ts for me.

import * as express from 'express';
import * as bodyParser from 'body-parser';
import * as multer from 'multer';

That’s your usual import block. Nothing serious going on here: we import Express and some middleware. Then we inject them in the constructor of App.js:

public express;constructor(
private upload = new multer({ dest: 'c:\\upload' }),
) {
this.start();
}

The upload path is entirely arbitrary here, but you got the point.

Now we have a Multer instance, let’s configure our Express instance.

private start() {  this.express = express();  //  Allow body parsing
this.express.use(bodyParser.json());
this.express.use(bodyParser.urlencoded({ extended: true }));
// Allow form-data parsing
this.express.use(this.upload.any());
}

That’s pretty much it. Now the app can receive and parse json, urlencoded and alsoform-data. Let’s set up the router after the configuration lines to see how it works:

const router = express.Router();router.all('/', (req, res) => { res.redirect('/upload'); });router.post('/upload', (req, res) => {
console.log(req.body);
res.status(200).send('Hello there!');
});
this.express.use('/', router);

Now when you click Send in Postman, you’ll get Hello there! as a HTTP response. At the same time you’ll see this on your console:

[Object: null prototype] { whatever: 'nothing' }

And if you look into your upload directory, which was c:\upload here (yes, I’m working on Windows) you’ll see your uploaded file under a temporary name. Read req.file for details.

What is happening here?

Multer has multiple methods of operation. Most tutorials are about receiving files, and they all suggest you to do this (remember, this.upload was the name of our injected Multer instance):

router.post('/upload',
this.upload.single('file'),
(req, res) => {
console.log(req.body);
res.status(200).send('Hello there!');
});

In other words, their suggestion is to use Multer only in the route where a file upload is expected, and use the .single() method to configure it.

Which is inherently wrong. If you do this, your route will only receive the file (and only if the field name is file in the above example) and never the form fields. You’ll keep getting console errors if you try to send anything else. With this solution you’ll have to submit all form data and file separately, with different encodings.

But if you use the .any() method, Multer will accept everything, including single or multiple file uploads. Actually there’s a whole bunch of these methods, and the difference is the number of files they handle.

  • multer.none() no files (0)
  • multer.single() one file (1)
  • multer.array() at least one file (1+)
  • multer.any() any number of files (0+)
  • multer.fields() at least one file (1+)

The difference between array() and fields() is how the file list will be processed for the req.files array.

With array() your req.files will be a simple array with all the files you uploaded.

With fields() you’ll get a string-indexed array with the individual filenames as keys.

Neither array() nor fields() can process text fields, so I’d suggest staying with all() unless you’re really only interested in uploaded files.

Wait a minute! Isn’t this insecure?

Oh look, you spotted the elephant in the room! Indeed, using multer.any() for every route is a bit risky because it allows all of your POST routes to receive file uploads. Even if no other harm is done, these files will sit in the temporary folder and consume server space, and it’s also easy to DDoS your server with multiple concurrent uploads.

So the actual solution is not to use a global Multer configuration, unless either none of your routes expect files, or all of them does. Instead you should use multer.any() for routes that are expected to receive both file uploads and form data. All other routes will do fine with multer.none(). Here’s what I mean:

// This route receives files and form data
router.post('/upload',
this.upload.any(),
(req, res) => { ... });
// This route receives only form data
router.post('/not_upload',
this.upload.none(),
(req, res) => { ... });

Of course it’s still recommended to process the upload, verify it and discard temporary files as soon as you’re done.

--

--