form-data vs busboy vs formidable vs multer vs express-fileupload
Handling File Uploads and Multipart Forms in Node.js
form-databusboyformidablemulterexpress-fileuploadSimilar Packages:

Handling File Uploads and Multipart Forms in Node.js

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.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
form-data108,837,9332,35783.9 kB1424 months agoMIT
busboy20,492,9732,999124 kB41--
formidable15,570,135-204 kB-2 years agoMIT
multer14,398,25912,08855.2 kB1633 days agoMIT
express-fileupload394,7671,556119 kB25a year agoMIT

Handling File Uploads in Node.js: A Deep Dive into busboy, multer, formidable, and more

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.

πŸ—οΈ Architecture Level: Low-Level Streams vs. Ready-Made Middleware

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
});

πŸ’Ύ Storage Strategies: Automatic vs. Manual Control

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);
});

🚦 Flow Control and Limits

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');
});

πŸ“‘ Progress Tracking and Events

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.

☁️ Use Case Scenarios

Scenario 1: Standard Express API

You are building a REST API with Express and need to save user avatars to disk.

  • βœ… Best Choice: multer
  • Why? It integrates perfectly with Express routes, handles disk storage configuration cleanly, and includes safety limits out of the box.

Scenario 2: Microservice or Vanilla HTTP Server

You are using a lightweight framework like Fastify or the native http module and need to parse uploads.

  • βœ… Best Choice: formidable or busboy
  • Why? formidable 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.

Scenario 3: Quick Prototype

You need to test a file upload endpoint in 5 minutes.

  • βœ… Best Choice: express-fileupload
  • Why? Minimal setup. Just app.use(fileUpload()) and access req.files. No storage config needed for basic tests.

Scenario 4: Uploading to a Third-Party API

Your server needs to forward an uploaded file to an external image processing service.

  • βœ… Best Choice: form-data
  • Why? This library is designed to construct the outgoing multipart request. You would pipe the incoming stream from busboy/multer into a form-data instance to forward it.

⚠️ Deprecation and Maintenance Notes

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.

πŸ“Š Summary Comparison

Featuremulterformidablebusboyexpress-fileuploadform-data
Primary RoleExpress MiddlewareStandalone ParserStreaming ParserExpress MiddlewareRequest Constructor
FrameworkExpress OnlyAny (Vanilla, Fastify, etc.)AnyExpress OnlyAny (Client-side)
StorageConfigurable (Disk/Memory)Auto-save to DirManual (Stream)Manual (.mv())N/A (Sends data)
Progress EventsNo (Native)YesManualNoN/A
Ease of UseHighMediumLow (Verbose)Very HighHigh
Best ForStandard Express AppsCustom Servers / ProgressHigh Perf / Cloud StreamsPrototypesSending Files

πŸ’‘ The Big Picture

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.

How to Choose: form-data vs busboy vs formidable vs multer vs express-fileupload

  • form-data:

    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.

  • busboy:

    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.

  • formidable:

    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.

  • multer:

    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.

  • express-fileupload:

    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.

README for form-data

Form-Data NPM Module Join the chat at https://gitter.im/form-data/form-data

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.

Linux Build MacOS Build Windows Build

Coverage Status Dependency Status

Install

npm install --save form-data

Usage

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.

Custom options

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

Alternative submission methods

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);
});

Methods

Void append( String field, Mixed value [, Mixed options] )

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 });

Headers getHeaders( [Headers userHeaders] )

This method adds the correct content-type header to the provided array of userHeaders.

String getBoundary()

Return the boundary of the formData. By default, the boundary consists of 26 - followed by 24 numbers for example:

--------------------------515890814546601021194782

Void setBoundary(String boundary)

Set the boundary string, overriding the default behavior described above.

Note: The boundary must be unique and may not appear in the data.

Buffer getBuffer()

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.

Integer getLengthSync()

Same as getLength but synchronous.

Note: getLengthSync doesn't calculate streams length.

Integer getLength(function callback )

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));

Boolean hasKnownLength()

Checks if the length of added values is known.

Request submit(params, function callback )

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();
} );

String toString()

Returns the form data as a string. Don't use this if you are sending files or buffers, use getBuffer() instead.

Integration with other libraries

Request

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.

node-fetch

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);
    });

axios

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)

Notes

  • 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.
  • Starting version 2.x FormData has dropped support for node@0.10.x.
  • Starting version 3.x FormData has dropped support for node@4.x.

License

Form-Data is released under the MIT license.