Two Sides of Every Pipe
Readable & Writable Streams
Readable produces data, Writable consumes it, Transform sits in the middle.
What you'll learn
- Author a custom Readable
- Author a custom Writable
- Use Transform for in-line processing
There are four stream types: Readable, Writable, Transform (Readable + Writable), and Duplex (rare). Most work happens with the first three.
Custom Readable
import { Readable } from "node:stream";
const numbers = Readable.from(async function* () {
for (let i = 1; i <= 5; i++) {
yield `n=${i}\n`;
}
}());
for await (const chunk of numbers) {
process.stdout.write(chunk);
} Readable.from(iterable) is the easy way to make a stream from any
iterable or async generator. Yields values become chunks.
Custom Writable
import { Writable } from "node:stream";
const upperWriter = new Writable({
write(chunk, encoding, callback) {
process.stdout.write(chunk.toString().toUpperCase());
callback();
},
});
upperWriter.write("hello\n");
upperWriter.write("world\n");
upperWriter.end(); write() is called for each chunk. Call callback() when done
(or with an error).
Transform — The Useful One
A Transform reads from one side and writes to the other:
import { Transform } from "node:stream";
import { pipeline } from "node:stream/promises";
import { createReadStream } from "node:fs";
const upper = new Transform({
transform(chunk, encoding, callback) {
this.push(chunk.toString().toUpperCase());
callback();
},
});
await pipeline(
createReadStream("input.txt"),
upper,
process.stdout
); Read file → uppercase → print. Streaming, low-memory.
Object Mode
By default streams shuttle Buffers/strings. For arbitrary objects,
pass objectMode: true:
import { Readable, Transform } from "node:stream";
const users = Readable.from([
{ id: 1, name: "Ada" },
{ id: 2, name: "Linus" },
]);
const greet = new Transform({
objectMode: true,
transform(user, _enc, cb) {
this.push(`Hello, ${user.name}\n`);
cb();
},
});
users.pipe(greet).pipe(process.stdout); Web Streams
Node 22 also supports the WHATWG Web Streams API (ReadableStream,
WritableStream) — the same API browsers use. For new code that
might run cross-platform, prefer those.