Body Size Limiting

Reusable, runtime-agnostic helpers for enforcing request body size limits.

The maxRequestBodySize option enforces a body size limit for the whole server. The same building blocks are exported from srvx/body-limit so downstream layers (for example per-route or per-handler limits) can enforce the same streaming semantics and error shape without re-implementing them.

These helpers use only web streams (no Node.js APIs), so they run on any runtime.

On Bun the server-level maxRequestBodySize is enforced natively by Bun.serve. These helpers still work anywhere for your own limits, but the srvx Node and Deno adapters are the ones that use them internally.

limitBodyStream

Wraps a body ReadableStream<Uint8Array> so the total number of bytes read cannot exceed the limit, returning a new stream.

import { limitBodyStream } from "srvx/body-limit";

const limited = limitBodyStream(request.body, 1024 * 1024 /* 1 MiB */);

It is pull-based: it reads from the upstream stream only when the consumer pulls, so it preserves backpressure and never buffers the whole body. As soon as the accumulated size passes the limit, the wrapped stream errors with a 413-style error and the upstream stream is cancelled with that same error, so the source can stop producing and release the socket. Cancelling the wrapped stream propagates the cancellation upstream.

limitRequestBody

Returns a request whose body is size-limited. A request without a body is returned unchanged; otherwise it is wrapped in a Proxy that routes every body read (body / text / json / formData / arrayBuffer / blob / bytes / bodyUsed) through a single size-limited stream and passes everything else through to the original request.

Because it wraps rather than rebuilds, the returned value is the same type as the input and keeps its identity: a ServerRequest stays a ServerRequest, with its augmentation (runtime, waitUntil, ip, context, …) intact.

import { limitRequestBody } from "srvx/body-limit";

const safeRequest = limitRequestBody(request, 1024 * 1024 /* 1 MiB */);
// reading the body throws the 413-style error if it exceeds the limit
const data = await safeRequest.json();

When the request declares a Content-Length that already exceeds the limit, the body is rejected early: the original body is cancelled without being read and the returned request's body errors immediately. Content-Length is only a fast path — it may be absent (chunked transfer encoding) or understated, so the streaming limit is always enforced regardless. The error surfaces when the body is consumed (request.text() / .json() / .arrayBuffer() / .body), matching the streamed-limit behaviour.

A request whose Content-Length overstates the actual body (e.g. it declares more bytes than it sends) is rejected on the declared length, even if the bytes that follow would fit — an overstated Content-Length is a malformed request, and this matches how servers such as Bun and nginx enforce their limits.

Error shape

Both helpers reject with the canonical error created by createBodyTooLargeError (typed as the exported BodyTooLargeError). It carries a stable, documented shape so any layer can map it to an HTTP 413 Payload Too Large response without string matching:

PropertyValue
code"ERR_BODY_TOO_LARGE"
statusCode413
status413
import { createBodyTooLargeError, limitRequestBody } from "srvx/body-limit";

const safeRequest = limitRequestBody(request, 1024 * 1024);
try {
  return Response.json(await safeRequest.json());
} catch (error) {
  if (error.code === "ERR_BODY_TOO_LARGE") {
    return new Response("Payload Too Large", { status: error.statusCode });
  }
  throw error;
}

// Or construct the error directly to enforce a limit elsewhere:
throw createBodyTooLargeError(1024 * 1024);

Custom error

Both helpers accept an options object whose createError overrides the error thrown on overflow — used for the streaming limit, the Content-Length fast path, and (for limitRequestBody) any clone(). This lets a framework map an overflow straight to its own error/response type instead of string-matching ERR_BODY_TOO_LARGE:

import { limitRequestBody } from "srvx/body-limit";

const safeRequest = limitRequestBody(request, 1024 * 1024, {
  createError: (max) => new MyHttpError(413, `Body exceeds ${max} bytes`),
});
// a raw `safeRequest.text()` overflow now throws `MyHttpError` directly

The factory receives the limit (bytes) and returns the value to throw. Omitting createError keeps the default 413-style error.