Node.js: Node.js Streams

Last updated: 2026-08-26

Charlie was assigned an urgent task: to analyze 2GB of access logs on the server. As soon as he loaded them using fs.readFile, the memory immediately ran out—causing an OOM crash. His team leader said, “No matter how big the file is, don’t try to swallow it all at once—use Stream to process it bit by bit!” Charlie switched to createReadStream to process the logs line by line, and the memory usage remained stable at 50MB. From then on, he told everyone he met: “Stream is the Swiss Army knife for processing big data in Node.js.”

1. Basic Concepts of Streams

Stream is an abstract interface in Node.js for processing streaming data, where data is transmitted in chunks, like a stream of water, rather than being loaded into memory all at once.

(1) Four Types of Flows

Type Description Typical Scenarios Input Output
Readable Readable stream, data source fs.createReadStream, process.stdin None Yes
Writable Writable stream, data endpoint fs.createWriteStream, process.stdout Yes No
Duplex Duplex, read/write (independent) net.Socket, tls.Socket Yes Yes
Transform Transformation stream; the output is a transformation of the input zlib.createGzip, crypto.createCipheriv Yes Yes

(2) Two Types of Flow

The Readable stream has two operating modes:

Feature Flowing Mode Paused Mode
Data Retrieval Automatic push; consume via the data event Must manually call read() to fetch
Trigger Method Add data listener / Call pipe() / Call resume() Initial default state / Call pause()
Backpressure Handling pipe() Automatic handling; manual operation requires monitoring drain Pacing controlled by the consumer, natural backpressure
Use Cases High-throughput, continuous data sources Requires precise control over read timing


2. A Detailed Explanation of Stream Events

All streams are based on EventEmitter and use an event-driven mechanism to pass data and state.

(1) General Events

Event Trigger Condition Applicable Flow Callback Parameters
data New chunk received Readable chunk
end Data read Readable None
error Error All streams Error
close Close Low-Level Resources All Streams None
finish end() After all data has been flushed Writable None

▶ Example: Listening for Readable stream events

JAVASCRIPT
const fs = require('fs');
const rs = fs.createReadStream('./access.log', { highWaterMark: 64 * 1024 });

rs.on('data', (chunk) => {
  console.log(`Received ${chunk.length} Byte`);
});

rs.on('end', () => {
  console.log('Finished reading');
});

rs.on('error', (err) => {
  console.error('Output error:', err.message);
});
▶ Try it Yourself

▶ Example: Listening for Writable stream events

JAVASCRIPT
const ws = fs.createWriteStream('./output.txt');

ws.on('finish', () => {
  console.log('All data has been written');
});

ws.on('error', (err) => {
  console.error('Write error:', err.message);
});

ws.write('Hello Stream');
ws.end();
▶ Try it Yourself

3. Chaining with pipe()

pipe() is Stream's most powerful method; it automatically connects the output of a readable stream to the input of a writable stream and automatically handles backpressure.

▶ Example: (1) Basic Usage of "pipe"

JAVASCRIPT
readable.pipe(writable);
▶ Try it Yourself

pipe() Returns the target stream, so it can be chained:

JAVASCRIPT
readable.pipe(transform1).pipe(transform2).pipe(writable);

▶ Example: Copying Files

JAVASCRIPT
const fs = require('fs');
fs.createReadStream('./source.txt')
  .pipe(fs.createWriteStream('./dest.txt'));
▶ Try it Yourself

▶ Example: Streaming HTTP Responses

JAVASCRIPT
const http = require('http');
const fs = require('fs');

http.createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  fs.createReadStream('./large.txt').pipe(res);
}).listen(3000);
▶ Try it Yourself

▶ Example: (2) Stream Pipe Data Flow Diagram

100%
flowchart LR
    A[Readable<br/>Data Source] -->|chunk| B[Transform<br/>Data Transformation]
    B -->|chunk| C[Writable<br/>Data Endpoint]
    C -.->|backpressure| A
    B -.->|backpressure| A

    style A fill:#4CAF50,color:#fff
    style B fill:#FF9800,color:#fff
    style C fill:#2196F3,color:#fff


4. fs Streams and File Operations

The fs module provides read and write streams related to the file system.

(1) createReadStream / createWriteStream

Option Description Default
highWaterMark Buffer size (bytes) Readable: 64KB / Writable: 16KB
encoding Encoding null (Buffer)
start Starting byte position 0
end End byte position (inclusive) Infinity
flags File open flags Readable: r / Writable: w

▶ Example: Reading a File Segment Within a Specified Range

JAVASCRIPT
const fs = require('fs');
const rs = fs.createReadStream('./big.bin', {
  start: 100,
  end: 199,
  highWaterMark: 32
});

rs.on('data', (chunk) => {
  console.log(chunk.length);
});
▶ Try it Yourself

(2) readFile vs createReadStream Comparison

Comparison readFile createReadStream
Memory Usage All files loaded into memory Uses only the highWaterMark size
Startup delay Wait until all files have been read before calling the callback Return the stream immediately and process as you read
Suitable File Sizes Small files (<10 MB) Large files or continuous data
Error Handling Retrieving an Error in a Callback Listening for error Events
Can be paused/resumed Not supported pause() / resume()

▶ Example: Processing Large Files Block by Block

JAVASCRIPT
const fs = require('fs');
let totalBytes = 0;

const rs = fs.createReadStream('./2gb.log');
rs.on('data', (chunk) => {
  totalBytes += chunk.length;
});
rs.on('end', () => {
  console.log(`Total ${totalBytes} Byte`);
});
▶ Try it Yourself

5. Backpressure Mechanism

Backpressure is at the heart of flow control: when the write side cannot keep up with the read side’s push rate, “backpressure” is applied to the write side to pause the read side and prevent memory backlog.

(1) pipe() automatically handles backpressure

When using pipe(), backpressure is automatically managed internally by Node.js, requiring no manual intervention.

(2) Manual Backpressure Control

When pipe() is not used, you must manually evaluate the return value of write():

JAVASCRIPT
const fs = require('fs');
const rs = fs.createReadStream('./source.txt');
const ws = fs.createWriteStream('./dest.txt');

rs.on('data', (chunk) => {
  const canContinue = ws.write(chunk);
  if (!canContinue) {
    rs.pause();
    ws.once('drain', () => {
      rs.resume();
    });
  }
});

rs.on('end', () => {
  ws.end();
});

▶ Example: Observing the Effect of Back Pressure

JAVASCRIPT
const rs = fs.createReadStream('./big.log', { highWaterMark: 1024 });
const ws = fs.createWriteStream('./out.log', { highWaterMark: 512 });

let paused = 0;
rs.on('data', (chunk) => {
  const ok = ws.write(chunk);
  if (!ok) {
    paused++;
    rs.pause();
    ws.once('drain', () => rs.resume());
  }
});
rs.on('end', () => {
  console.log(`Backpressure triggered ${paused} times`);
  ws.end();
});
▶ Try it Yourself

6. Transform Stream

A Transform stream is a subclass of Duplex; its output is the transformed result of the input, and it is commonly used for data compression, encryption, and format conversion.

(1) The Difference Between Transform and Duplex

Comparison Item Duplex Transform
Input-Output Relationship Independent; do not affect each other Output is generated by transforming the input
Required Methods _read() + _write() _transform()
Typical Uses Network Sockets Compression, Encryption, Data Conversion
Internal buffer One for reading, one for writing Intermediate state of transformation

▶ Example: Custom Transform Stream (Case Conversion)

JAVASCRIPT
const { Transform } = require('stream');

const upper = new Transform({
  transform(chunk, encoding, callback) {
    callback(null, chunk.toString().toUpperCase());
  }
});

process.stdin.pipe(upper).pipe(process.stdout);
▶ Try it Yourself

▶ Example: zlib-compressed files

JAVASCRIPT
const fs = require('fs');
const zlib = require('zlib');

fs.createReadStream('./access.log')
  .pipe(zlib.createGzip())
  .pipe(fs.createWriteStream('./access.log.gz'));
▶ Try it Yourself

▶ Example: Decompressing a zlib file

JAVASCRIPT
const fs = require('fs');
const zlib = require('zlib');

fs.createReadStream('./access.log.gz')
  .pipe(zlib.createGunzip())
  .pipe(fs.createWriteStream('./access_restored.log'));
▶ Try it Yourself

7. Pausing and Resuming a Stream

The Readable stream is in pause mode by default and can be toggled using pause() and resume().

▶ Example: Pause and Resume Controls

JAVASCRIPT
const fs = require('fs');
const rs = fs.createReadStream('./big.log');
let count = 0;

rs.on('data', (chunk) => {
  count++;
  if (count % 10 === 0) {
    rs.pause();
    console.log(`Processed ${count} chunks, pause 1 second`);
    setTimeout(() => rs.resume(), 1000);
  }
});
▶ Try it Yourself

▶ Example: Reading a file line by line (readline)

JAVASCRIPT
const fs = require('fs');
const readline = require('readline');

const rl = readline.createInterface({
  input: fs.createReadStream('./access.log'),
  crlfDelay: Infinity
});

rl.on('line', (line) => {
  if (line.includes('ERROR')) {
    console.log(line);
  }
});

rl.on('close', () => {
  console.log('The file has been read.');
});
▶ Try it Yourself

8. Comprehensive Example: Log Processing Pipeline

Build a complete data processing pipeline: Read large log files → Parse line by line → Filter out error lines → Compress the output.

JAVASCRIPT
const fs = require('fs');
const zlib = require('zlib');
const { Transform } = require('stream');

const filterError = new Transform({
  transform(chunk, encoding, callback) {
    const lines = chunk.toString().split('\n');
    const errors = lines.filter(l => l.includes('ERROR')).join('\n');
    callback(null, errors ? errors + '\n' : '');
  }
});

const src = fs.createReadStream('./app.log');
const dest = fs.createWriteStream('./errors.log.gz');

src
  .pipe(filterError)
  .pipe(zlib.createGzip())
  .pipe(dest);

dest.on('finish', () => {
  console.log('The error log has been written in compressed form.');
});

src.on('error', (err) => {
  console.error('Read failed:', err.message);
});

❓ FAQ

Q What is a stream?
A A stream is an abstract interface for processing data; data can be read or written in chunks without having to be loaded into memory all at once.
Q What is the difference between a Readable stream and a Writable stream?
A A Readable stream is a data source from which data can be read; a Writable stream is a data destination to which data can be written.
Q When should streams be used?
A In scenarios such as handling large files, network transmission, and real-time data processing—when the data volume is large or cannot be loaded into memory all at once.
Q What does the pipe method do?
A pipe connects the output of a readable stream to the input of a writable stream, automatically managing data flow and backpressure.
Q What is backpressure?
A When the write stream’s data output rate is slower than the read stream’s data input rate, the write stream sends a signal to the read stream to pause reading; this is the backpressure mechanism.

📖 Summary


📝 Exercises

  1. Complete all the code examples in this lesson and make sure each one runs correctly.
  2. Modify the comprehensive example and add your own extensions
  3. Review the official documentation, identify 1–2 APIs not covered in this lesson, and write test code for them.
  4. Reflection: How would you apply what you’ve learned in this lesson to a real-world project?
  5. Try to combine what you’ve learned in this lesson with material from previous lessons to build a small project.
Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏