Readable & Writable Patterns
Readable and Writable streams share modes and flow control primitives - choose byte vs object mode up front and match producer chunk sizes to consumer throughput.
Search across all documentation pages
Readable and Writable streams share modes and flow control primitives - choose byte vs object mode up front and match producer chunk sizes to consumer throughput.
import { Readable, Writable } from 'node:stream';
// Byte mode (default)
const byteStream = Readable.from([Buffer.from('ab'), Buffer.from('cd')]);
// Object mode
const objectStream = Readable.from([{ id: 1 }, { id: 2 }], { objectMode: true });When to reach for this:
highWaterMark for memory vs latencyReadable from async data sourceimport { Readable, Writable } from 'node:stream';
class CounterSource extends Readable {
private i = 0;
constructor(private max: number, opts?: ConstructorParameters<typeof Readable>[0]) {
super({ objectMode: true, ...opts });
}
_read(): void {
if (this.i >= this.max) {
this.push(null);
return;
}
this.push({ n: this.i++ });
}
}
const batchWriter = new Writable({
objectMode: true,
write(obj: { n: number }, _enc, cb) {
// simulate DB batch insert
setImmediate(cb);
},
});
const source = new CounterSource(1000);
source.pipe(batchWriter);// Paused mode - explicit read()
const r = Readable.from(['a', 'b', 'c']);
r.on('readable', () => {
let chunk;
while ((chunk = r.read()) !== null) {
console.log(chunk);
}
});What this demonstrates:
Readable implements _read and pushes until null ends streamWritable receives typed objects - batch in _write or accumulate for bulk insertreadable event + read() - useful for parsing framingdata event or pipe) is default for most I/Odata events or pipe.read(n) to pull chunks.highWaterMark - internal buffer threshold; exceeding triggers backpressure (false from write).cork/uncork on Writable batches small writes into fewer syscalls.| Mode | Chunk type | highWaterMark unit | Typical use |
|---|---|---|---|
| Byte (default) | Buffer/string | bytes | Files, HTTP, gzip |
| objectMode | any JS value | object count | Records, events |
import { Readable } from 'node:stream';
function linesFromFile(path: string): Readable {
return Readable.from(
(async function* () {
const { createReadStream } = await import('node:fs');
const { createInterface } = await import('node:readline');
const rl = createInterface({ input: createReadStream(path) });
for await (const line of rl) yield line;
})(),
);
}null - throws. Fix: track ended state in custom Readable.write return false - memory blowup. Fix: wait for drain event.highWaterMark on high-latency I/O - syscall overhead. Fix: benchmark 16-64 KB for files.| Alternative | Use When | Don't Use When |
|---|---|---|
for await on async iterable | Simple consumption | Need backpressure to slow disk |
readline interface | Line-based text | Binary protocols |
| EventEmitter chunks | Legacy code | Greenfield pipelines |
| Web ReadableStream | fetch body interop | Pure node:fs path |
16 KiB for byte streams, 16 objects for object mode - adjust per workload.
When you need precise control over consumption rate or partial reads for framing.
Yes - long sync _write blocks event loop - offload or use worker threads.
Writable hook after last chunk - flush buffers, complete async work before finish event.
Both sides must use objectMode or Transform between byte and object worlds.
from for iterables; subclass when pull source needs stateful _read logic.
Produces string chunks instead of Buffers - match downstream expectations.
Abrupt teardown with optional error - pipeline calls on failure paths.
Independent read/write sides - TCP sockets - see Transform & Duplex.
Use tee pattern or PassThrough branches - never double-pipe one readable without split.
Call cb() when done - or return Promise from _write in modern stream API variants.
response.body is Web ReadableStream - convert with Readable.fromWeb when needed.
Stack versions: This page was written for Node.js 24.18.0 (Active LTS), npm 10+, TypeScript 5.6+, Express 5, Fastify 5, and NestJS 11.
Reviewed by Chris St. John·Last updated Jul 19, 2026