These five libraries address the challenge of parsing multipart/form-data requests in Node.js, a standard format for uploading files and submitting forms. busboy is a low-level, high-performance streaming parser that serves as the engine for many higher-level tools. multer is the most popular middleware for Express, built on top of busboy, offering easy disk and memory storage configuration. formidable is a robust, standalone parser known for its reliability and progress tracking, often used outside of Express. express-fileupload provides a simple, express-specific interface that mimics the old req.files object without requiring middleware setup. Finally, form-data is distinct from the others; it is primarily used to construct and send multipart requests (acting as a client) rather than parsing incoming ones, though it is often grouped in this ecosystem due to its name and related functionality.
Uploading files is a common requirement in web applications, but handling multipart/form-data in Node.js can be tricky. Unlike simple JSON payloads, file uploads involve streaming binary data, managing boundaries, and handling large payloads efficiently. The ecosystem offers several tools, each with a specific role. Let's break down how busboy, multer, formidable, express-fileupload, and form-data differ and when to use them.
The most important distinction is where these libraries sit in your stack. Some are raw engines, while others are complete solutions.
busboy is a streaming parser. It does not save files or manage storage. It simply parses the incoming stream and emits events for files and fields. You must write the logic to pipe these streams to disk or memory.
// busboy: Manual stream handling
import busboy from 'busboy';
function handler(req, res) {
const bb = busboy({ headers: req.headers });
bb.on('file', (name, file, info) => {
// You must manually pipe to a destination
file.pipe(require('fs').createWriteStream(`./uploads/${info.filename}`));
});
req.pipe(bb);
}
multer is Express middleware built on top of busboy. It handles the parsing AND the storage logic automatically. You configure a "storage engine," and it takes care of the rest.
// multer: Configured middleware
import multer from 'multer';
const upload = multer({ dest: 'uploads/' });
app.post('/profile', upload.single('avatar'), (req, res) => {
// File is already saved; req.file contains metadata
res.send('Upload complete');
});
formidable is a standalone parser similar to busboy but includes built-in file writing capabilities. It works with any Node.js HTTP server, not just Express.
// formidable: Standalone with auto-saving
import { IncomingForm } from 'formidable';
const form = new IncomingForm({ uploadDir: './uploads' });
form.parse(req, (err, fields, files) => {
// Files are automatically written to uploadDir
console.log(files.avatar.filepath);
});
express-fileupload is a lightweight Express middleware that adds a files property to the request object. It simplifies the API but lacks the advanced storage configuration of multer.
// express-fileupload: Simple property injection
import fileUpload from 'express-fileupload';
app.use(fileUpload());
app.post('/upload', (req, res) => {
// Direct access to file data
req.files.avatar.mv('./uploads/avatar.png');
});
form-data is different. It is used to create multipart requests, not parse them. You use this when your Node.js server needs to act as a client and upload a file to another service.
// form-data: Constructing an outgoing request
import FormData from 'form-data';
import fetch from 'node-fetch';
const form = new FormData();
form.append('avatar', require('fs').createReadStream('./pic.png'));
fetch('https://api.example.com/upload', {
method: 'POST',
body: form
});
How you save files is often the deciding factor. multer shines here with its pluggable storage engines.
multer allows you to swap between memory storage and disk storage easily, and even define custom filenames.
// multer: Disk storage with custom filename
const storage = multer.diskStorage({
destination: (req, file, cb) => cb(null, 'uploads/'),
filename: (req, file, cb) => cb(null, `${Date.now()}-${file.originalname}`)
});
const upload = multer({ storage });
express-fileupload uses a simpler approach. Files are temporarily stored, and you must explicitly call .mv() to move them permanently. If you forget, temp files might linger.
// express-fileupload: Explicit move required
if (req.files && req.files.photo) {
req.files.photo.mv('/permanent/path/photo.png');
}
formidable automatically saves files to a configured directory but gives you the final path in the callback. It handles cleanup of temporary names internally.
// formidable: Auto-save to configured dir
const form = new IncomingForm({
uploadDir: './data',
keepExtensions: true
});
// File saved automatically, path returned in 'files' object
busboy gives you zero storage logic. You get a stream, and you decide where it goes. This is powerful for uploading directly to cloud storage (like S3) without touching the local disk.
// busboy: Streaming directly to S3 (conceptual)
bb.on('file', (name, file) => {
s3UploadStream.write(file);
});
Production apps need to prevent abuse. You must limit file sizes and counts.
multer has robust built-in limits for file size, number of files, and field count.
// multer: Enforcing limits
const upload = multer({
limits: {
fileSize: 5 * 1024 * 1024, // 5MB
files: 1
}
});
formidable also supports limits, configured via the constructor options.
// formidable: Setting max file size
const form = new IncomingForm({
maxFileSize: 5 * 1024 * 1024,
maxFiles: 1
});
busboy requires you to handle limits manually by listening to the limit event or checking stream sizes, offering more flexibility but more code.
// busboy: Manual limit handling
bb.on('limit', () => {
// Handle file size or count limit exceeded
res.status(413).send('Too large');
});
For large files, users want to see progress bars. Not all libraries support this easily.
formidable excels here. It emits progress events naturally, making it easy to track upload percentage.
// formidable: Progress events
form.on('progress', (bytesReceived, bytesExpected) => {
const percent = (bytesReceived / bytesExpected) * 100;
console.log(`Upload: ${percent}%`);
});
multer and express-fileupload do not emit progress events by default because they rely on the underlying stream handling of Express/Busboy. Implementing progress requires dropping down to lower-level stream events or using additional middleware.
busboy allows you to calculate progress manually by tracking byte counts on the file stream, but it requires custom implementation.
You are building a REST API with Express and need to save user avatars to disk.
multerYou are using a lightweight framework like Fastify or the native http module and need to parse uploads.
formidable or busboyformidable offers a good balance of features without Express dependencies. busboy is better if you need to stream directly to another service (like AWS S3) without saving to the local filesystem first.You need to test a file upload endpoint in 5 minutes.
express-fileuploadapp.use(fileUpload()) and access req.files. No storage config needed for basic tests.Your server needs to forward an uploaded file to an external image processing service.
form-databusboy/multer into a form-data instance to forward it.It is critical to note the maintenance status of these libraries:
formidable: Historically very stable, but version 1.x had known issues. Version 2.x and 3.x are modernized and actively maintained. Do not use v1.x; ensure you install the latest version.multer: Widely used and stable, but development has slowed. It is still the standard for Express, but be aware that major updates are rare. It is safe for production but verify compatibility with newer Node versions if using edge features.busboy: Actively maintained and the recommended low-level engine by the Node.js community.express-fileupload: Maintained, but simpler. Suitable for less critical paths.form-data: Essential tool, actively maintained, and standard for client-side multipart construction.| Feature | multer | formidable | busboy | express-fileupload | form-data |
|---|---|---|---|---|---|
| Primary Role | Express Middleware | Standalone Parser | Streaming Parser | Express Middleware | Request Constructor |
| Framework | Express Only | Any (Vanilla, Fastify, etc.) | Any | Express Only | Any (Client-side) |
| Storage | Configurable (Disk/Memory) | Auto-save to Dir | Manual (Stream) | Manual (.mv()) | N/A (Sends data) |
| Progress Events | No (Native) | Yes | Manual | No | N/A |
| Ease of Use | High | Medium | Low (Verbose) | Very High | High |
| Best For | Standard Express Apps | Custom Servers / Progress | High Perf / Cloud Streams | Prototypes | Sending Files |
Choosing the right tool depends on your server architecture and how much control you need.
multer is the pragmatic choice for most Express developers. It removes boilerplate and handles the messy parts of file storage securely. If you are in the Express ecosystem, start here.
formidable is the robust alternative for non-Express environments or when you need upload progress tracking. It strikes a great balance between power and ease of use.
busboy is the specialist's tool. Use it when you need to stream files directly to cloud storage, process video/audio in real-time, or build your own high-performance upload server.
express-fileupload is the quick fix. Great for hacks, prototypes, or internal tools where advanced storage logic isn't needed.
form-data is the companion tool. While the others help you receive files, form-data helps you send them. You will likely use this alongside one of the parsers when your app needs to proxy uploads to other services.
Final Thought: Avoid reinventing the wheel. For standard web apps, multer or formidable will cover 95% of your needs safely and efficiently. Only drop down to busboy if you have specific streaming requirements that higher-level abstractions cannot meet.
Choose form-data when your application needs to send multipart requests to other APIs or servers, rather than receive them. It is the standard tool for constructing HTTP POST requests with files and fields in Node.js scripts, microservices, or testing suites.
Choose busboy if you need maximum performance and fine-grained control over the streaming process without the overhead of a full framework. It is ideal for building custom upload handlers, processing large files in real-time, or integrating into non-Express servers like Fastify or Koa where you need to manually pipe streams.
Choose formidable if you need a battle-tested, framework-agnostic parser that supports upload progress events and works well with vanilla Node.js http servers. It is a strong choice for applications requiring detailed feedback on upload status or those avoiding Express-specific middleware.
Choose multer if you are building an Express application and need a reliable, feature-rich solution for saving files to disk or memory with minimal code. It is the industry standard for Express file uploads, offering built-in support for file filtering, limits, and multiple storage strategies.
Choose express-fileupload if you want the simplest possible setup for an Express app and prefer accessing files directly via req.files without configuring storage engines. It is best for small projects, prototypes, or scenarios where files are small enough to be held in memory without complex disk management logic.
A library to create readable "multipart/form-data" streams. Can be used to submit forms and file uploads to other web applications.
The API of this library is inspired by the XMLHttpRequest-2 FormData Interface.
npm install --save form-data
In this example we are constructing a form with 3 fields that contain a string, a buffer and a file stream.
var FormData = require('form-data');
var fs = require('fs');
var form = new FormData();
form.append('my_field', 'my value');
form.append('my_buffer', new Buffer(10));
form.append('my_file', fs.createReadStream('/foo/bar.jpg'));
Also you can use http-response stream:
var FormData = require('form-data');
var http = require('http');
var form = new FormData();
http.request('http://nodejs.org/images/logo.png', function (response) {
form.append('my_field', 'my value');
form.append('my_buffer', new Buffer(10));
form.append('my_logo', response);
});
Or @mikeal's request stream:
var FormData = require('form-data');
var request = require('request');
var form = new FormData();
form.append('my_field', 'my value');
form.append('my_buffer', new Buffer(10));
form.append('my_logo', request('http://nodejs.org/images/logo.png'));
In order to submit this form to a web application, call submit(url, [callback]) method:
form.submit('http://example.org/', function (err, res) {
// res β response object (http.IncomingMessage) //
res.resume();
});
For more advanced request manipulations submit() method returns http.ClientRequest object, or you can choose from one of the alternative submission methods.
You can provide custom options, such as maxDataSize:
var FormData = require('form-data');
var form = new FormData({ maxDataSize: 20971520 });
form.append('my_field', 'my value');
form.append('my_buffer', /* something big */);
List of available options could be found in combined-stream
You can use node's http client interface:
var http = require('http');
var request = http.request({
method: 'post',
host: 'example.org',
path: '/upload',
headers: form.getHeaders()
});
form.pipe(request);
request.on('response', function (res) {
console.log(res.statusCode);
});
Or if you would prefer the 'Content-Length' header to be set for you:
form.submit('example.org/upload', function (err, res) {
console.log(res.statusCode);
});
To use custom headers and pre-known length in parts:
var CRLF = '\r\n';
var form = new FormData();
var options = {
header: CRLF + '--' + form.getBoundary() + CRLF + 'X-Custom-Header: 123' + CRLF + CRLF,
knownLength: 1
};
form.append('my_buffer', buffer, options);
form.submit('http://example.com/', function (err, res) {
if (err) throw err;
console.log('Done');
});
Form-Data can recognize and fetch all the required information from common types of streams (fs.readStream, http.response and mikeal's request), for some other types of streams you'd need to provide "file"-related information manually:
someModule.stream(function (err, stdout, stderr) {
if (err) throw err;
var form = new FormData();
form.append('file', stdout, {
filename: 'unicycle.jpg', // ... or:
filepath: 'photos/toys/unicycle.jpg',
contentType: 'image/jpeg',
knownLength: 19806
});
form.submit('http://example.com/', function (err, res) {
if (err) throw err;
console.log('Done');
});
});
The filepath property overrides filename and may contain a relative path. This is typically used when uploading multiple files from a directory.
For edge cases, like POST request to URL with query string or to pass HTTP auth credentials, object can be passed to form.submit() as first parameter:
form.submit({
host: 'example.com',
path: '/probably.php?extra=params',
auth: 'username:password'
}, function (err, res) {
console.log(res.statusCode);
});
In case you need to also send custom HTTP headers with the POST request, you can use the headers key in first parameter of form.submit():
form.submit({
host: 'example.com',
path: '/surelynot.php',
headers: { 'x-test-header': 'test-header-value' }
}, function (err, res) {
console.log(res.statusCode);
});
Append data to the form. You can submit about any format (string, integer, boolean, buffer, etc.). However, Arrays are not supported and need to be turned into strings by the user.
var form = new FormData();
form.append('my_string', 'my value');
form.append('my_integer', 1);
form.append('my_boolean', true);
form.append('my_buffer', new Buffer(10));
form.append('my_array_as_json', JSON.stringify(['bird', 'cute']));
You may provide a string for options, or an object.
// Set filename by providing a string for options
form.append('my_file', fs.createReadStream('/foo/bar.jpg'), 'bar.jpg');
// provide an object.
form.append('my_file', fs.createReadStream('/foo/bar.jpg'), { filename: 'bar.jpg', contentType: 'image/jpeg', knownLength: 19806 });
This method adds the correct content-type header to the provided array of userHeaders.
Return the boundary of the formData. By default, the boundary consists of 26 - followed by 24 numbers
for example:
--------------------------515890814546601021194782
Set the boundary string, overriding the default behavior described above.
Note: The boundary must be unique and may not appear in the data.
Return the full formdata request package, as a Buffer. You can insert this Buffer in e.g. Axios to send multipart data.
var form = new FormData();
form.append('my_buffer', Buffer.from([0x4a,0x42,0x20,0x52,0x6f,0x63,0x6b,0x73]));
form.append('my_file', fs.readFileSync('/foo/bar.jpg'));
axios.post('https://example.com/path/to/api', form.getBuffer(), form.getHeaders());
Note: Because the output is of type Buffer, you can only append types that are accepted by Buffer: string, Buffer, ArrayBuffer, Array, or Array-like Object. A ReadStream for example will result in an error.
Same as getLength but synchronous.
Note: getLengthSync doesn't calculate streams length.
Returns the Content-Length async. The callback is used to handle errors and continue once the length has been calculated
this.getLength(function (err, length) {
if (err) {
this._error(err);
return;
}
// add content length
request.setHeader('Content-Length', length);
...
}.bind(this));
Checks if the length of added values is known.
Submit the form to a web application.
var form = new FormData();
form.append('my_string', 'Hello World');
form.submit('http://example.com/', function (err, res) {
// res β response object (http.IncomingMessage) //
res.resume();
} );
Returns the form data as a string. Don't use this if you are sending files or buffers, use getBuffer() instead.
Form submission using request:
var formData = {
my_field: 'my_value',
my_file: fs.createReadStream(__dirname + '/unicycle.jpg'),
};
request.post({url:'http://service.com/upload', formData: formData}, function (err, httpResponse, body) {
if (err) {
return console.error('upload failed:', err);
}
console.log('Upload successful! Server responded with:', body);
});
For more details see request readme.
You can also submit a form using node-fetch:
var form = new FormData();
form.append('a', 1);
fetch('http://example.com', { method: 'POST', body: form })
.then(function (res) {
return res.json();
}).then(function (json) {
console.log(json);
});
In Node.js you can post a file using axios:
const form = new FormData();
const stream = fs.createReadStream(PATH_TO_FILE);
form.append('image', stream);
// In Node.js environment you need to set boundary in the header field 'Content-Type' by calling method `getHeaders`
const formHeaders = form.getHeaders();
axios.post('http://example.com', form, {
headers: {
...formHeaders,
},
})
.then(response => response)
.catch(error => error)
getLengthSync() method DOESN'T calculate length for streams, use knownLength options as workaround.getLength(cb) will send an error as first parameter of callback if stream length cannot be calculated (e.g. send in custom streams w/o using knownLength).submit will not add content-length if form length is unknown or not calculable.2.x FormData has dropped support for node@0.10.x.3.x FormData has dropped support for node@4.x.Form-Data is released under the MIT license.