@primafuture/request-body-parser
v2.0.0
Published
A reusable streaming request-body parser for multipart, urlencoded, and raw HTTP bodies.
Maintainers
Readme
@primafuture/request-body-parser
A reusable streaming request-body parser for Node.js HTTP servers.
It supports:
multipart/form-dataapplication/x-www-form-urlencoded- raw passthrough mode for unsupported content types
The library is single-pass and stream-first:
- file parts are exposed as streams
- field parts are also exposed as streams
- full field values and full files are not buffered in memory by the parser
Why this exists
This package keeps the proven low-level parsing ideas from busboy style parsers, but exposes a cleaner modern API:
- parts are the main abstraction
- both files and fields are streamed
- completion and failure are represented with promises
- unsupported content types do not throw, they fall back to raw mode
Install
Published package:
npm install @primafuture/request-body-parserInside this local package:
npm installBuild the library:
npm run buildTypecheck only:
npm run typecheckThis package builds to:
- CommonJS:
dist/index.cjs - ESM:
dist/index.mjs - Type declarations:
dist/index.d.ts
Quick Example
import { createServer } from 'node:http';
import { createIncomingRequestBodyParser } from '@primafuture/request-body-parser';
createServer((req, res) => {
const parser = createIncomingRequestBodyParser(req);
parser.onPart((part) => {
let totalBytes = 0;
part.stream.on('data', (chunk: Buffer) => {
totalBytes += chunk.length;
});
part.completed.then(() => {
if (part.kind === 'file') {
console.log('file', part.name, part.filename, totalBytes);
} else {
console.log('field', part.name, part.charset, totalBytes);
}
}).catch((error) => {
console.error('part failed', error);
});
});
parser.completed.then(() => {
res.statusCode = 200;
res.end('ok\n');
}).catch((error) => {
res.statusCode = 400;
res.end(`${String(error)}\n`);
});
}).listen(8080);Public API
createIncomingRequestBodyParser(req, options?)
Creates a parser for a single IncomingMessage.
import type { IncomingMessage } from 'node:http';
export function createIncomingRequestBodyParser(
req: IncomingMessage,
options?: Partial<IRequestBodyParserOptions>,
): IRequestBodyParser;IRequestBodyParser
export interface IRequestBodyParser {
bodyStream: IncomingMessage;
completed: Promise<void>;
contentType: string | null;
failed: Promise<unknown>;
mode: 'multipart' | 'raw' | 'urlencoded';
onPart(listener: (part: TRequestBodyPart) => void): () => void;
}Fields:
bodyStream: the original request body streamcontentType: normalized content type likemultipart/form-data, ornullmode: parsing mode chosen from the incoming content typecompleted: resolves when parser-level processing of the request body completesfailed: resolves with the error when parser-level processing failsonPart(...): subscribes to streamed parts and returns an unsubscribe function
TRequestBodyPart
export type TRequestBodyPart = IRequestBodyFieldPart | IRequestBodyFilePart;Shared fields:
kind:'field' | 'file'name: field name from the formheaders: normalized part headerscontentType: part content type ornulltransferEncoding: part transfer encoding ornullstream: readable stream for this part bodycompleted: resolves when that specific readable part stream ends for the consumer
Field-specific fields:
charset
File-specific fields:
filename
Options
export interface IRequestBodyParserOptions {
defaultCharset: string;
defaultParameterCharset: string;
maxFieldNameSize: number;
maxHeaderPairs: number;
maxPartHeadersSize: number;
preserveFilePath: boolean;
}Default values:
defaultCharset:'utf-8'defaultParameterCharset:'latin1'maxFieldNameSize:100maxHeaderPairs:2000maxPartHeadersSize:16384preserveFilePath:false
Supported Modes
Multipart
For multipart/form-data:
- boundaries are parsed incrementally
- each part is emitted as either a file part or a field part
- part headers are parsed with explicit size and pair limits
- file and field bodies are streamed without full buffering
File parts are currently identified by the presence of filename or filename* in Content-Disposition.
Urlencoded
For application/x-www-form-urlencoded:
- names are decoded incrementally
- values are emitted as streamed field bodies
- percent-decoding across chunk boundaries is handled correctly
+is decoded as space
Raw
For unsupported or malformed content types:
mode === 'raw'- no parsed parts are emitted
- the original request stream remains available as
bodyStream
Important Behavior Notes
- For parsed modes, consume emitted part streams. If you stop reading them, backpressure will correctly stall parsing.
parser.completedtracks parser/request completion. It does not wait for user code to fully drain every emitted part stream.part.completedtracks the exposed readable stream for that individual part.- Destroying an emitted part stream cancels that part, rejects its
completedpromise, and lets the parser continue with later parts when the body format allows it. failedis kept as a compatibility mirror for parser-level failures.- In raw mode, this library does not parse anything for you.
- In parsed modes, do not separately consume
bodyStreamas if it were an independent second stream. The parser reads from the original request body once.
Smoke Server
A tiny local test server is included in examples/smoke.ts.
Start it:
npm run smokeOr in watch mode:
npm run smoke:watchThe server listens on http://localhost:8338.
Multipart test
curl -F 'title=hello' -F 'upload=@./package.json' http://localhost:8338Urlencoded test
curl \
-H 'content-type: application/x-www-form-urlencoded' \
--data 'name=alice¬e=hello+world' \
http://localhost:8338The smoke server:
- prints parser activity to the console
- writes a short per-part summary into the HTTP response
Packaging
This package is prepared for scoped publishing as @primafuture/request-body-parser.
Published entrypoints:
require('@primafuture/request-body-parser')->dist/index.cjsimport '@primafuture/request-body-parser'->dist/index.mjs- TypeScript declarations ->
dist/index.d.ts
Current Limitations
- There is no built-in aggregation of field values or files. That is intentional; downstream code should decide how to collect, sample, cap, or store data.
- There are no built-in multipart part count, file count, or size limits yet beyond multipart header limits and urlencoded field-name length limits.
- The parser is built for Node.js streams and
IncomingMessage; it is not a Web Streams API package.
