npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@maroonedsoftware/multipart

v1.0.0

Published

A robust multipart form-data and multipart/related parser for Node.js HTTP servers.

Readme

@maroonedsoftware/multipart

A robust multipart form-data and multipart/related parser for Node.js HTTP servers. Built on top of @fastify/busboy with a promise-based API and sensible defaults.

Installation

pnpm add @maroonedsoftware/multipart

Features

  • Promise-based API – Clean async/await interface for parsing multipart requests
  • Configurable limits – Protect against resource exhaustion with file size, count, and field limits
  • Stream-based file handling – Process files efficiently without loading entire files into memory
  • Type-safe – Full TypeScript support with comprehensive type definitions
  • Automatic cleanup – Properly removes event listeners to prevent memory leaks

Quick Start

import { createServer, IncomingMessage, ServerResponse } from 'node:http';
import { createWriteStream } from 'node:fs';
import { pipeline } from 'node:stream/promises';
import { MultipartBody, isMultipartFieldData } from '@maroonedsoftware/multipart';

const server = createServer(async (req: IncomingMessage, res: ServerResponse) => {
  if (req.method === 'POST' && req.headers['content-type']?.includes('multipart/form-data')) {
    const multipart = new MultipartBody(req);

    const fields = await multipart.parse(async (fieldname, stream, filename) => {
      // Save uploaded file to disk
      await pipeline(stream, createWriteStream(`./uploads/${filename}`));
    });

    // Access form fields
    const description = fields.get('description');
    if (description && isMultipartFieldData(description)) {
      console.log('Description:', description.value);
    }

    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ success: true }));
  }
});

server.listen(3000);

API Reference

MultipartBody

The main class for parsing multipart/form-data requests.

Constructor

new MultipartBody(req: IncomingMessage, limits?: MultipartLimits)

| Parameter | Type | Description | | --------- | ----------------- | ----------------------------------- | | req | IncomingMessage | The incoming HTTP request | | limits | MultipartLimits | Optional default limits for parsing |

Default limits:

  • files: 1
  • fileSize: 20 MB

parse(fileHandler, limits?)

Parses the multipart request body.

parse(
  fileHandler: FileHandler,
  limits?: MultipartLimits
): Promise<Map<string, MultipartData | MultipartData[]>>

| Parameter | Type | Description | | ------------- | ----------------- | ------------------------------------- | | fileHandler | FileHandler | Callback invoked for each file upload | | limits | MultipartLimits | Optional per-request limit overrides |

Returns: A Map where keys are field names and values are either a single MultipartData object or an array if multiple values were submitted.

FileHandler

Callback type for handling file uploads:

type FileHandler = (fieldname: string, stream: Readable, filename: string, encoding: string, mimeType: string) => Promise<void>;

MultipartLimits

Configuration options for limiting request sizes:

| Option | Type | Default | Description | | --------------- | -------- | -------- | -------------------------------- | | fieldNameSize | number | 100 | Max field name size in bytes | | fieldSize | number | 1 MB | Max field value size in bytes | | fields | number | Infinity | Max number of non-file fields | | fileSize | number | Infinity | Max file size in bytes | | files | number | Infinity | Max number of file fields | | parts | number | Infinity | Max total parts (fields + files) | | headerPairs | number | 2000 | Max header key-value pairs | | headerSize | number | 81920 | Max header part size in bytes |

Type Guards

isMultipartFieldData(data)

Type guard to check if parsed data is a form field.

if (isMultipartFieldData(data)) {
  console.log(data.value); // TypeScript knows this is FieldData
}

isMultipartFileData(data)

Type guard to check if parsed data is a file.

if (isMultipartFileData(data)) {
  data.stream.pipe(destination); // TypeScript knows this is FileData
}

Data Types

FieldData

type FieldData = {
  value: string;
  nameTruncated: boolean;
  valueTruncated: boolean;
  encoding: string;
  mimeType: string;
};

FileData

type FileData = {
  stream: Readable;
  filename: string;
  encoding: string;
  mimeType: string;
};

Advanced Usage

Custom File Size Limits

// Set default limits in constructor
const multipart = new MultipartBody(req, {
  files: 5,
  fileSize: 50 * 1024 * 1024, // 50 MB
});

// Override per-request
const fields = await multipart.parse(fileHandler, {
  fileSize: 100 * 1024 * 1024, // 100 MB for this request only
});

Handling Multiple Files

const multipart = new MultipartBody(req, { files: 10 });

const uploadedFiles: string[] = [];

const fields = await multipart.parse(async (fieldname, stream, filename) => {
  const path = `./uploads/${Date.now()}-${filename}`;
  await pipeline(stream, createWriteStream(path));
  uploadedFiles.push(path);
});

console.log('Uploaded files:', uploadedFiles);

Error Handling

The parser throws HTTP 413 errors when limits are exceeded:

import { MultipartBody } from '@maroonedsoftware/multipart';

try {
  const fields = await multipart.parse(fileHandler);
} catch (error) {
  if (error.statusCode === 413) {
    // Handle limit exceeded
    console.error('Upload too large:', error.internalDetails?.reason);
  }
  throw error;
}

Example

type ParsedMultipartBody = {
  fields: Map<string, MultipartData>;
  file: {
    filename: string;
    stream: Readable;
    encoding: string;
    mimeType: string;
  };
};

export class Service {
  private async parseMultipartBody(multipartReq: MultipartBody) {
    let file: ParsedMultipartBody['file'] | undefined;
    const fields = await multipartReq.parse(async (fieldname, stream, fileFieldName, encoding, mimeType) => {
      if (fieldname === 'file') {
        file = { stream, filename: fileFieldName, encoding, mimeType };
      }
    });
    return { fields, file } as ParsedMultipartBody;
  }

  async handle(body: MultipartBody): Promise<void> {
    const parsedBody = await this.parseMultipartBody(body);
    filename = parsedBody.file?.filename ?? 'missing name';
    content = parsedBody.file?.stream;

    // use content
  }
}

License

MIT