← back to blog

Tutorials

Handling Large File Uploads with Node.js Streams

Buffering a 2GB upload into memory is a great way to meet your on-call rotation. Streams fix it in about thirty lines.

Edwine Nyakundi

Edwine Nyakundi

Full stack engineer · Jul 19, 2026 · 8 min read

The naive upload handler reads the whole body, then writes it somewhere. It works beautifully until a user uploads a video and your container gets OOM-killed.

Pipe, don't collect

js
import { pipeline } from class="tok-string">"node:stream/promises";
import { createWriteStream } from class="tok-string">"node:fs";

export async function handleUpload(req, res) {
  const target = createWriteStream(class="tok-string">`/tmp/${crypto.randomUUID()}`);

  try {
    await pipeline(req, target);
    res.writeHead(201).end(class="tok-string">"uploaded");
  } catch (err) {
    target.destroy();
    res.writeHead(500).end(class="tok-string">"upload failed");
  }
}

`pipeline` handles backpressure and cleanup for you. That second part matters more than people expect — a half-written temp file left behind on every failed request is its own outage waiting to happen.

Add a size guard

js
import { Transform } from class="tok-string">"node:stream";

const limit = (max) => {
  let seen = 0;
  return new Transform({
    transform(chunk, _enc, cb) {
      seen += chunk.length;
      if (seen > max) return cb(new Error(class="tok-string">"payload too large"));
      cb(null, chunk);
    },
  });
};

Drop that in the middle of the pipeline and you reject oversized uploads before they cost you disk.

#Node.js#Streams#Backend

Related posts

Newsletter

Get new posts in your inbox

Deep dives on TypeScript, Postgres, and engineering careers — roughly twice a month. No courses to sell, no spam.