Open chat

← All posts

Node.jsstreams

Node.js streams: memory-safe handling of large uploads

Why buffering entire request bodies fails at scale, and how pipeline and backpressure keep the process stable under load.

Holding a multi-gigabyte upload in a single Buffer is a predictable path to GC pressure, OOM risk, and tail latency. Node’s stream APIs let you process data incrementally and respect backpressure so producers slow when consumers are saturated.

Prefer stream/promises pipeline

pipeline wires readable → writable, propagates errors, and handles drain correctly—safer than manual pipe in production code.

import type { IncomingMessage, ServerResponse } from "http";
import { createWriteStream } from "fs";
import { pipeline } from "stream/promises";

export async function persistUpload(
  req: IncomingMessage,
  res: ServerResponse,
  filePath: string,
) {
  try {
    await pipeline(req, createWriteStream(filePath));
    res.writeHead(201, { "Content-Type": "application/json" });
    res.end(JSON.stringify({ ok: true }));
  } catch {
    res.writeHead(500, { "Content-Type": "application/json" });
    res.end(JSON.stringify({ ok: false, error: "upload_failed" }));
  }
}

Policy at the edge

Configure reverse proxies (client_max_body_size or equivalent) to match application limits. Without alignment, failures surface as opaque disconnects instead of a controlled 413.

Takeaway: Streams are not an optimization—they are how you bound memory per request while preserving throughput.

← Back to portfolio