Back to Blog
Node.js2026-04-24

Node.js Streams Tutorial: From Basics to Backpressure

Process gigabytes of data in constant memory using Node streams.

Streams let Node process datasets larger than RAM. Used correctly, they keep memory flat regardless of input size.

The Four Stream Types

- Readable — fs.createReadStream, http response - Writable — fs.createWriteStream, http request - Duplex — TCP socket - Transform — gzip, crypto cipher

Pipeline Over Pipe

The classic .pipe() method silently drops errors. Use pipeline from node:stream/promises:

``js import { pipeline } from 'node:stream/promises'; import { createReadStream, createWriteStream } from 'node:fs'; import { createGzip } from 'node:zlib';

await pipeline( createReadStream('huge.log'), createGzip(), createWriteStream('huge.log.gz') ); `

This compresses any file size in constant memory and propagates errors correctly.

Backpressure Explained

When a writable cannot keep up, write() returns false. A well-behaved producer pauses until the drain event. The pipeline helper handles this for you. Manual implementations look like:

`js function pump(src, dst) { src.on('data', chunk => { if (!dst.write(chunk)) src.pause(); }); dst.on('drain', () => src.resume()); } `

Async Iteration

Modern code uses async iterators instead of .on('data'):

`js for await (const chunk of fs.createReadStream('input.txt')) { process(chunk); } `

This naturally respects backpressure.

Web Streams Compatibility

Node 21+ ships standard Web Streams (ReadableStream, WritableStream). They interop with Node streams via Readable.toWeb() and Readable.fromWeb()`. Use them when you target both browsers and Node.

For more on async patterns, see [async/await deep dive](/blog/async-await-deep-dive).