Readable & Writable Streams

Two Sides of Every Pipe

Readable & Writable Streams

Readable produces data, Writable consumes it, Transform sits in the middle.

4 min read Level 2/5 #nodejs#streams#readable
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.

EventEmitter →