javascript 110 lines · 3 tabs

Streaming Multipart Form Upload Parser Using Busboy in Node.js

Shared by codesnips Aug 2026
3 tabs
const Busboy = require('busboy');
const { storeFile } = require('./storage');

function parseMultipart(req, { maxFileSize = 20 * 1024 * 1024 } = {}) {
  return new Promise((resolve, reject) => {
    const busboy = Busboy({
      headers: req.headers,
      limits: { fileSize: maxFileSize, files: 10 },
    });

    const fields = {};
    const files = [];
    const pending = [];

    busboy.on('field', (name, value) => {
      fields[name] = value;
    });

    busboy.on('file', (name, stream, info) => {
      const { sink, meta } = storeFile(info);
      let truncated = false;

      stream.on('limit', () => {
        truncated = true;
        stream.resume(); // drain remaining bytes so parsing can complete
      });

      const done = new Promise((res, rej) => {
        sink.on('finish', () => res({ ...meta, field: name, truncated }));
        sink.on('error', rej);
        stream.on('error', rej);
      });

      pending.push(done);
      stream.pipe(sink);
    });

    busboy.on('error', reject);
    busboy.on('close', () => {
      Promise.all(pending)
        .then((stored) => resolve({ fields, files: stored }))
        .catch(reject);
    });

    req.pipe(busboy);
  });
}

module.exports = { parseMultipart };
3 files · javascript Explain with highlit

This snippet shows how to accept file uploads without buffering the entire request into memory by consuming the incoming request stream in chunks and parsing it on the fly. Multipart bodies interleave form fields and file bytes separated by a boundary token declared in the Content-Type header. Naively collecting the whole body into a Buffer scales poorly and lets a client exhaust memory with a large upload, so the parser reads the socket incrementally instead.

In multipartParser.js, parseMultipart wraps the callback-driven busboy library in a Promise. Busboy is itself a Writable stream, so the request is piped into it and it emits a file event per file part and a field event per scalar field. Rather than accumulating file bytes, each file stream is piped straight into a sink returned by storeFile, preserving backpressure end to end: if storage is slow, the pipe pauses reading from the socket automatically. The limits option enforces a fileSize cap; when exceeded busboy emits limit on the file stream, and the code marks the upload truncated and resumes the stream so parsing can finish cleanly instead of hanging. The close event resolves once every part has drained, and any stream error rejects the promise.

In storage.js, storeFile creates a crypto.randomUUID()-named write target under an uploads directory and returns both the WritableStream and the eventual metadata. Using createWriteStream keeps bytes flowing to disk in chunks that mirror what arrives on the wire.

In uploadRoute.js, an Express handler validates the content-type up front, delegates to parseMultipart, and responds with the collected field values and stored file descriptors. Errors from oversized or malformed uploads surface as a 400.

The key trade-off is complexity for safety: event-driven streaming code is harder to reason about than reading req.body, but it bounds memory regardless of upload size and applies natural backpressure. A common pitfall is forgetting to consume or resume a file stream that hit its limit, which stalls the whole parse; the handler here always drains. This pattern is the right choice whenever uploads may be large, concurrent, or untrusted.


Related snips

Share this code

Here's the card — post it anywhere.

Streaming Multipart Form Upload Parser Using Busboy in Node.js — share card
Link copied