or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.md
tile.json

tessl/npm-busboy

A streaming parser for HTML form data for node.js

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
npmpkg:npm/busboy@1.6.x

To install, run

npx @tessl/cli install tessl/npm-busboy@1.6.0

index.mddocs/

Busboy

Busboy is a high-performance streaming parser for HTML form data in Node.js applications. It provides efficient parsing of multipart/form-data and application/x-www-form-urlencoded content types with configurable security limits and full event-based API support for handling file uploads and form fields.

Package Information

  • Package Name: busboy
  • Package Type: npm
  • Language: JavaScript
  • Installation: npm install busboy

Core Imports

const busboy = require("busboy");

For ES modules:

import busboy from "busboy";

Basic Usage

const http = require("http");
const busboy = require("busboy");

http.createServer((req, res) => {
  if (req.method === "POST") {
    const bb = busboy({ headers: req.headers });
    
    bb.on("file", (name, file, info) => {
      const { filename, encoding, mimeType } = info;
      console.log(`File [${name}]: filename: ${filename}, encoding: ${encoding}, mimeType: ${mimeType}`);
      
      file.on("data", (data) => {
        console.log(`File [${name}] got ${data.length} bytes`);
      }).on("close", () => {
        console.log(`File [${name}] done`);
      });
    });
    
    bb.on("field", (name, val, info) => {
      console.log(`Field [${name}]: value: ${val}`);
    });
    
    bb.on("close", () => {
      console.log("Done parsing form!");
      res.writeHead(303, { Connection: "close", Location: "/" });
      res.end();
    });
    
    req.pipe(bb);
  }
}).listen(8000);

Architecture

Busboy is built around a streaming parser architecture:

  • Factory Function: Single entry point that creates appropriate parser instances based on Content-Type
  • Parser Selection: Automatically detects and instantiates multipart or URL-encoded parsers
  • Event-Driven API: Uses Node.js EventEmitter pattern for handling parsed data
  • Streaming Processing: Memory-efficient processing through Node.js streams
  • Security Limits: Configurable limits to prevent abuse and excessive resource usage

Capabilities

Parser Creation

Creates a streaming parser instance based on the Content-Type header.

/**
 * Creates and returns a new Writable form parser stream
 * @param {BusboyConfig} config - Configuration object with headers and options
 * @returns {Writable} Parser stream (Multipart or URLEncoded)
 * @throws {Error} If Content-Type is missing or unsupported
 */
function busboy(config);

interface BusboyConfig {
  /** HTTP headers of the incoming request (must include 'content-type') */
  headers: { [key: string]: string };
  /** highWaterMark for parser stream */
  highWaterMark?: number;
  /** highWaterMark for individual file streams */
  fileHwm?: number;
  /** Default character set when not defined (default: 'utf8') */
  defCharset?: string;
  /** Default charset for multipart parameter values (default: undefined, no decoding) */
  defParamCharset?: string;
  /** Preserve paths in filenames for multipart (default: false) */
  preservePath?: boolean;
  /** Various limits on incoming data */
  limits?: BusboyLimits;
}

interface BusboyLimits {
  /** Max field name size in bytes (default: 100) */
  fieldNameSize?: number;
  /** Max field value size in bytes (default: 1048576) */
  fieldSize?: number;
  /** Max number of non-file fields (default: Infinity) */
  fields?: number;
  /** Max file size in bytes for multipart (default: Infinity) */
  fileSize?: number;
  /** Max number of file fields for multipart (default: Infinity) */
  files?: number;
  /** Max number of parts for multipart (default: Infinity) */
  parts?: number;
  /** Max number of header key-value pairs for multipart (default: 2000) */
  headerPairs?: number;
}

File Event Handling

Emitted for each file found in multipart/form-data requests.

/**
 * File event - emitted for each new file found
 * @param {string} name - Form field name
 * @param {Readable} stream - File data stream with possible 'truncated' property
 * @param {FileInfo} info - File metadata
 */
parser.on("file", (name, stream, info) => {});

interface FileInfo {
  /** File's filename (WARNING: validate before use) */
  filename?: string;
  /** File's Content-Transfer-Encoding value */
  encoding: string;
  /** File's Content-Type value */
  mimeType: string;
}

Usage Example:

bb.on("file", (name, file, info) => {
  const { filename, encoding, mimeType } = info;
  
  // Always consume the stream to prevent hanging
  file.on("data", (data) => {
    // Process file data chunk
    console.log(`Received ${data.length} bytes`);
  });
  
  file.on("limit", () => {
    console.log("File size limit reached, file truncated");
  });
  
  file.on("close", () => {
    console.log("File stream closed");
    // Check file.truncated property if needed
    if (file.truncated) {
      console.log("File was truncated due to size limit");
    }
  });
});

Field Event Handling

Emitted for each non-file field found in form data.

/**
 * Field event - emitted for each new non-file field found
 * @param {string} name - Form field name
 * @param {string} value - Field value
 * @param {FieldInfo} info - Field metadata
 */
parser.on("field", (name, value, info) => {});

interface FieldInfo {
  /** Whether field name was truncated due to limits */
  nameTruncated: boolean;
  /** Whether field value was truncated due to limits */
  valueTruncated: boolean;
  /** Field's Content-Transfer-Encoding value */
  encoding: string;
  /** Field's Content-Type value */
  mimeType: string;
}

Usage Example:

bb.on("field", (name, val, info) => {
  console.log(`Field [${name}]: ${val}`);
  
  if (info.nameTruncated) {
    console.log("Field name was truncated");
  }
  
  if (info.valueTruncated) {
    console.log("Field value was truncated");
  }
});

Limit Event Handling

Events emitted when configured limits are reached.

/**
 * Parts limit event - emitted when configured parts limit reached
 * No more 'file' or 'field' events will be emitted
 */
parser.on("partsLimit", () => {});

/**
 * Files limit event - emitted when configured files limit reached
 * No more 'file' events will be emitted
 */
parser.on("filesLimit", () => {});

/**
 * Fields limit event - emitted when configured fields limit reached
 * No more 'field' events will be emitted
 */
parser.on("fieldsLimit", () => {});

Usage Example:

const bb = busboy({ 
  headers: req.headers,
  limits: {
    files: 3,
    fields: 10,
    parts: 15
  }
});

bb.on("filesLimit", () => {
  console.log("Maximum number of files reached");
});

bb.on("fieldsLimit", () => {
  console.log("Maximum number of fields reached");
});

bb.on("partsLimit", () => {
  console.log("Maximum number of parts reached");
});

Stream Event Handling

Standard Node.js stream events for completing form processing.

/**
 * Close event - emitted when parsing is complete
 * All files and fields have been processed
 */
parser.on("close", () => {});

/**
 * Finish event - emitted when all data has been written to the parser
 * Occurs before close event
 */
parser.on("finish", () => {});

/**
 * Error event - emitted when parsing errors occur
 * @param {Error} error - Error object with details
 */
parser.on("error", (error) => {});

Usage Example:

bb.on("close", () => {
  console.log("Form parsing completed successfully");
  res.writeHead(200, { "Content-Type": "text/plain" });
  res.end("Upload complete!");
});

bb.on("error", (err) => {
  console.error("Parsing error:", err);
  res.writeHead(400, { "Content-Type": "text/plain" });
  res.end("Bad request");
});

// Pipe the request to busboy to start parsing
req.pipe(bb);

Stream Methods

Since busboy returns a Writable stream, it supports standard stream methods.

/**
 * Write data to the parser
 * @param {Buffer|string} chunk - Data chunk to parse
 * @param {string} encoding - String encoding (if chunk is string)
 * @param {Function} callback - Callback when write completes
 * @returns {boolean} Whether stream can accept more data
 */
parser.write(chunk, encoding, callback);

/**
 * Signal end of input data
 * @param {Buffer|string} chunk - Optional final chunk
 * @param {string} encoding - String encoding (if chunk is string)
 * @param {Function} callback - Callback when end completes
 */
parser.end(chunk, encoding, callback);

/**
 * Destroy the parser stream
 * @param {Error} error - Optional error to emit
 */
parser.destroy(error);

Error Handling

Busboy throws or emits errors in the following scenarios:

  • Missing Content-Type: When headers['content-type'] is missing or invalid
  • Unsupported Content-Type: When content type is not multipart/form-data or application/x-www-form-urlencoded
  • Missing Boundary: When multipart data lacks required boundary parameter
  • Malformed Data: When form data structure is invalid or corrupted
  • Unexpected End: When form data ends unexpectedly during parsing

Common Error Handling Pattern:

try {
  const bb = busboy({ headers: req.headers });
  
  bb.on("error", (err) => {
    console.error("Busboy parsing error:", err.message);
    if (!res.headersSent) {
      res.writeHead(400, { "Content-Type": "text/plain" });
      res.end("Invalid form data");
    }
  });
  
  req.pipe(bb);
} catch (err) {
  console.error("Busboy creation error:", err.message);
  res.writeHead(400, { "Content-Type": "text/plain" });
  res.end("Invalid request");
}

Advanced Usage Patterns

File Upload with Disk Storage

const fs = require("fs");
const path = require("path");
const { randomBytes } = require("crypto");

bb.on("file", (name, file, info) => {
  // Generate safe filename
  const ext = path.extname(info.filename || "");
  const filename = randomBytes(16).toString("hex") + ext;
  const saveTo = path.join("/uploads", filename);
  
  const writeStream = fs.createWriteStream(saveTo);
  file.pipe(writeStream);
  
  writeStream.on("close", () => {
    console.log(`File ${name} saved to ${saveTo}`);
  });
});

Memory Buffer Collection

const files = {};
const fields = {};

bb.on("file", (name, file, info) => {
  const chunks = [];
  
  file.on("data", (data) => {
    chunks.push(data);
  });
  
  file.on("close", () => {
    files[name] = {
      buffer: Buffer.concat(chunks),
      info: info
    };
  });
});

bb.on("field", (name, val, info) => {
  fields[name] = { value: val, info: info };
});

bb.on("close", () => {
  console.log("Collected files:", Object.keys(files));
  console.log("Collected fields:", Object.keys(fields));
});

Security Configuration

const bb = busboy({
  headers: req.headers,
  limits: {
    fieldNameSize: 100,       // Limit field name length
    fieldSize: 1024 * 1024,   // 1MB field value limit
    fields: 20,               // Max 20 fields
    fileSize: 10 * 1024 * 1024, // 10MB file limit
    files: 5,                 // Max 5 files
    parts: 25,                // Max 25 total parts
    headerPairs: 100          // Limit header pairs (multipart only)
  }
});