Node.js Streams Cheat Sheet

Backpressure, pipeline and why you should stop using .pipe()

Streams process data without holding it in memory. They are also where Node applications leak, hang and lose errors — almost always for the same three reasons.

Intermediate 1 min read 10 Entries Version 1.0 Sabir Updated 1
Download PDF Export Markdown Export HTML

Types

Type Does Example
Readable Produces data fs.createReadStream, an HTTP request
Writable Consumes data fs.createWriteStream, an HTTP response
Duplex Both, independently A TCP socket
Transform Both, output derived from input zlib.createGzip

Composition

Pattern Note
await pipeline(src, transform, dest) The right default — propagates errors and cleans up
src.pipe(dest) Does NOT forward errors and leaks on failure
for await (const chunk of readable) Async iteration; honours backpressure automatically
Readable.from(asyncGenerator()) Turn any async generator into a stream
stream.destroy(err) Tear down and propagate

Backpressure

writable.write() returns false when its buffer is full. Ignoring that return value is how a fast reader and a slow writer turn into unbounded memory growth — the data has to go somewhere, and it goes into RAM. pipeline() and for-await both handle this for you; a hand-rolled loop must wait for the "drain" event itself.

Code examples

pipeline() with a transform

The composition that propagates errors and cleans up. .pipe() does neither.

JavaScript pipeline.mjs Download
import { pipeline } from 'node:stream/promises';
import { Transform } from 'node:stream';
import { createReadStream, createWriteStream } from 'node:fs';
import { createGzip } from 'node:zlib';

const upper = new Transform({
  transform(chunk, _enc, cb) {
    cb(null, chunk.toString().toUpperCase());
  },
});

try {
  await pipeline(
    createReadStream('in.txt'),
    upper,
    createGzip(),
    createWriteStream('out.txt.gz'),
  );
  console.log('done');
} catch (err) {
  console.error('stream failed:', err);   // reachable, unlike with .pipe()
}

Frequently asked questions

Why should I stop using .pipe()?
It does not forward errors and does not clean up on failure, so a broken destination leaks the source handle and your catch block never runs. pipeline() fixes both.
What is backpressure, practically?
write() returning false means the buffer is full. Ignoring it means the unread data piles up in memory. pipeline() and for-await both handle it; a hand-rolled loop must wait for "drain".

Was this cheat sheet useful?

Comments

No comments yet — be the first.

Need a different cheat sheet? Tell us what you would like to see and we will build it — free.
Request a cheat sheet