Skip to content
⚠️ This article was written in 2019. Some content may be outdated.

Practical Guide to Node.js Streams

Streams are one of the most core modules in Node.js, but many developers only use fs.readFile for file processing on a daily basis. When you need to process large files, build pipeline-style data processing, or implement efficient I/O, Streams are an indispensable tool.

Why You Need Streams

Consider a common problem: reading a 2 GB log file.

javascript
// Option 1: fs.readFile — reads everything into memory at once
const fs = require("fs");

fs.readFile("./huge-log.txt", (err, data) => {
  if (err) throw err;
  console.log(data.length);
});

// Problem: a 2 GB file needs 2 GB of memory — likely to cause OOM
javascript
// Option 2: Stream — reads in chunks, constant memory usage
const fs = require("fs");

const stream = fs.createReadStream("./huge-log.txt", {
  encoding: "utf8",
  highWaterMark: 64 * 1024, // read 64 KB at a time
});

let totalSize = 0;
stream.on("data", (chunk) => {
  totalSize += chunk.length;
  // only 64 KB in memory at a time
});

stream.on("end", () => {
  console.log("Total size:", totalSize);
});

stream.on("error", (err) => {
  console.error("Read error:", err);
});

Streams split data into small chunks, changing memory usage from O(n) to O(1).

Four Types of Streams

javascript
const { Readable, Writable, Duplex, Transform } = require("stream");

Readable Stream

javascript
const { Readable } = require("stream");

class CounterStream extends Readable {
  constructor(max) {
    super({ encoding: "utf8" });
    this.max = max;
    this.current = 1;
  }

  _read() {
    if (this.current > this.max) {
      this.push(null); // null signals end of stream
      return;
    }
    this.push(`Line ${this.current}\n`);
    this.current++;
  }
}

const counter = new CounterStream(5);
counter.pipe(process.stdout);

Transform Stream

javascript
const { Transform } = require("stream");

class UpperCaseTransform extends Transform {
  _transform(chunk, encoding, callback) {
    this.push(chunk.toString().toUpperCase());
    callback();
  }
}

// Pipeline: read file → uppercase → write file
const fs = require("fs");
fs.createReadStream("input.txt")
  .pipe(new UpperCaseTransform())
  .pipe(fs.createWriteStream("output.txt"));

pipeline() — The Safe Way to Chain Streams

javascript
const { pipeline } = require("stream");
const { promisify } = require("util");
const pipelineAsync = promisify(pipeline);

async function processFile() {
  await pipelineAsync(
    fs.createReadStream("input.txt"),
    new UpperCaseTransform(),
    fs.createWriteStream("output.txt"),
  );
  // pipeline automatically handles error propagation and cleanup
}

Use pipeline instead of manual .pipe() chains — it properly handles backpressure and error cleanup.

MIT Licensed