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

@nextrush/multipart

v3.0.5

Published

Streaming multipart/form-data parser and file upload middleware for NextRush

Readme

@nextrush/multipart

Zero-dependency multipart/form-data file upload middleware for NextRush. Uses Web Streams API — works on Node.js, Bun, Deno, and Edge runtimes.

Features

  • Zero dependencies — custom streaming parser, no busboy or formidable
  • Web Streams API — uses ReadableStream, works on all runtimes
  • Pluggable storage — MemoryStorage (default) or DiskStorage, or bring your own
  • Security by default — filename sanitization, path traversal prevention, prototype pollution protection, body size enforcement
  • Size limits — configurable per-file, total files, fields, parts, and total body size
  • MIME type filtering — exact match and wildcard (image/*) support
  • Zero-config — works out of the box with sensible defaults

Installation

pnpm add @nextrush/multipart

Quick Start

import { createApp } from '@nextrush/core';
import { createRouter } from '@nextrush/router';
import { multipart } from '@nextrush/multipart';

const app = createApp();
const router = createRouter();

// Add multipart middleware
app.use(multipart());

router.post('/upload', (ctx) => {
  const { files, fields } = ctx.state;
  ctx.json({
    uploaded: files.map((f) => ({
      name: f.sanitizedName,
      size: f.size,
      type: f.mimeType,
    })),
    fields,
  });
});

app.route('/', router);
app.listen(3000);

Configuration

import { multipart, MemoryStorage, DiskStorage } from '@nextrush/multipart';

// Memory storage (default)
app.use(multipart());

// Disk storage
app.use(
  multipart({
    storage: new DiskStorage({
      dest: './uploads',
    }),
  })
);

// Full options
app.use(
  multipart({
    storage: new MemoryStorage(),
    allowedTypes: ['image/*', 'application/pdf'],
    abortOnError: true,
    limits: {
      maxFileSize: '10mb',
      maxFiles: 5,
      maxFields: 20,
      maxFieldSize: '1mb',
      maxBodySize: '50mb',
    },
  })
);

Storage Strategies

MemoryStorage (Default)

Buffers file contents in memory. Best for small files or when you need immediate access to the buffer.

⚠️ Production note: MemoryStorage holds the entire file in memory. For production workloads with large uploads, use DiskStorage or a custom StorageStrategy to avoid memory pressure. Always configure limits.maxFileSize and limits.maxBodySize to cap memory usage.

import { MemoryStorage } from '@nextrush/multipart';

app.use(multipart({ storage: new MemoryStorage() }));

// Access the buffer
router.post('/upload', (ctx) => {
  const file = ctx.state.files[0];
  console.log(file.buffer); // Uint8Array
});

DiskStorage

Streams files directly to the filesystem. Best for large files or persistent storage.

import { DiskStorage } from '@nextrush/multipart';

app.use(
  multipart({
    storage: new DiskStorage({
      dest: './uploads',
      filename: (info) => `${Date.now()}-${info.sanitizedName}`,
    }),
  })
);

// Access the file path
router.post('/upload', (ctx) => {
  const file = ctx.state.files[0];
  console.log(file.path); // './uploads/1234567890-photo.jpg'
});

Default filename uses crypto.randomUUID() prefix for collision resistance.

Custom Storage

Implement the StorageStrategy interface:

import type { StorageStrategy, StorageResult, FileInfo } from '@nextrush/multipart';

class S3Storage implements StorageStrategy {
  async handle(stream: ReadableStream<Uint8Array>, info: FileInfo): Promise<StorageResult> {
    // Upload to S3...
    return { size: uploadedBytes, path: s3Key };
  }

  async remove(result: StorageResult): Promise<void> {
    // Delete from S3...
  }
}

Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | storage | StorageStrategy | MemoryStorage | Where to store uploaded files | | allowedTypes | string[] | undefined | Allowed MIME types (supports wildcards) | | abortOnError | boolean | true | Stop processing on first error | | filename | (info: FileInfo) => string | undefined | Custom filename generator | | limits.maxFileSize | number \| string | '5mb' | Max size per file | | limits.maxFiles | number | 10 | Max number of files | | limits.maxFields | number | 50 | Max number of fields | | limits.maxParts | number | 100 | Max total parts | | limits.maxFieldSize | number \| string | '1mb' | Max size per field value | | limits.maxFieldNameSize | number | 200 | Max field name length | | limits.maxHeaderPairs | number | 2000 | Max header pairs per part | | limits.maxBodySize | number \| string | '10mb' | Max total request body size |

Accessing Uploaded Data

After the middleware runs, uploaded files and fields are available on ctx.state:

router.post('/upload', (ctx) => {
  // Files
  for (const file of ctx.state.files) {
    file.fieldName; // Form field name
    file.originalName; // Client-provided filename
    file.sanitizedName; // Safe filename for storage
    file.mimeType; // MIME type (e.g., 'image/png')
    file.encoding; // Transfer encoding
    file.size; // File size in bytes
    file.truncated; // Whether the file was truncated (exceeded size limit)
    file.buffer; // Uint8Array (MemoryStorage only)
    file.path; // File path on disk (DiskStorage only)
  }

  // Fields
  const { name, description } = ctx.state.fields;
});

Error Handling

The middleware throws MultipartError with specific error codes:

import { MultipartError } from '@nextrush/multipart';

app.use(async (ctx) => {
  try {
    await ctx.next();
  } catch (error) {
    if (error instanceof MultipartError) {
      ctx.status = error.status;
      ctx.json({
        error: error.code,
        message: error.message,
      });
    }
  }
});

Error Codes

| Code | Status | Description | |------|--------|-------------| | FILE_TOO_LARGE | 413 | File exceeds maxFileSize | | BODY_SIZE_EXCEEDED | 413 | Total body exceeds maxBodySize | | FILES_LIMIT_EXCEEDED | 413 | Too many files | | FIELDS_LIMIT_EXCEEDED | 413 | Too many fields | | PARTS_LIMIT_EXCEEDED | 413 | Too many parts | | INVALID_CONTENT_TYPE | 415 | Not a multipart/form-data request | | INVALID_FIELD_NAME | 400 | Prototype pollution attempt | | INVALID_FILE_TYPE | 415 | MIME type not in allowedTypes | | STORAGE_ERROR | 500 | Storage strategy failure | | PARSE_ERROR | 400 | Malformed multipart data | | REQUEST_ABORTED | 400 | Client disconnected |

Security

All protections are enabled by default — no opt-in required.

  • Path traversal prevention: Filenames are stripped of directory components
  • Null byte injection: Null bytes and control characters are replaced
  • Prototype pollution: Field names like __proto__, constructor, prototype are rejected
  • Hidden file prevention: Leading dots are stripped from filenames
  • Windows reserved names: CON, PRN, AUX, NUL, COM1COM9, LPT1LPT9 are prefixed with _
  • Body size enforcement: Total request body tracked cumulatively against maxBodySize
  • Boundary validation: Boundaries exceeding 70 characters rejected per RFC 2046
  • Error message sanitization: User-supplied values truncated and stripped of control characters
  • Size limits: All limits enforced at the streaming level

Runtime Compatibility

| Runtime | MemoryStorage | DiskStorage | Notes | |---------|---------------|-------------|-------| | Node.js 22+ | ✅ | ✅ | Full support | | Bun | ✅ | ✅ | Full support | | Deno | ✅ | ✅ | Via Node compat layer | | Cloudflare Workers | ✅ | ❌ | No filesystem access | | Vercel Edge | ✅ | ❌ | No filesystem access |

The parser uses Web Streams API (ReadableStream) and Web Crypto API (crypto.randomUUID()) — both available across all modern runtimes. DiskStorage requires node:fs and node:stream, limiting it to server runtimes.

License

MIT