Node.js Streams Tutorial: From Basics to Backpressure
Process gigabytes of data in constant memory using Node streams.
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).