Node.js Stream Events Reference

Readable, Writable, Duplex and Transform stream events with state machine notes.

Searchable Node.js stream event reference covering Readable, Writable, Duplex and Transform events, their emission conditions, backpressure signals and listening order. It runs free in your browser on Gera Tools, with nothing uploaded.

Last updated Source: Gera Tools

What is the difference between the 'end' and 'finish' events?

'end' fires on a Readable stream when there is no more data to consume. 'finish' fires on a Writable stream after stream.end() is called and all data has been flushed to the underlying system. A Duplex stream can emit both.

Node.js stream events at a glance

Node.js streams are EventEmitter instances, and most of the control flow you write around them is driven by events rather than return values. Knowing exactly when data, end, drain, finish and close fire — and on which kind of stream — is what separates a leaky pipe from a robust one. This reference lists every standard stream event, the stream types that emit it, and the precise condition that triggers it.

The four stream shapes

Readable — a source of data. Events: data, readable, end, close, error, pause, resume.

Writable — a destination for data. Events: drain, finish, pipe, unpipe, close, error.

Duplex — both a Readable and a Writable independently (for example, a TCP socket). Emits the union of both sets.

Transform — a Duplex whose output is derived from its input (for example, a gzip stream). Same events as Duplex, plus the internal _transform and _flush hooks.

Key events explained

data (Readable)

Fires every time a chunk is available when the stream is in flowing mode. Attaching a data listener automatically switches a Readable into flowing mode and starts pulling data. Removing all data listeners or calling stream.pause() switches it back to paused mode.

readable.on('data', (chunk) => {
  process.stdout.write(chunk);
});

readable (Readable)

The lower-level alternative to data. Fires when data is available in the internal buffer, but does not start the flow automatically — you call stream.read(n) yourself to pull chunks. This gives precise control over how much you consume at a time.

end (Readable)

Fires once when there is no more data to consume. On a file read stream this is when the last byte has been delivered. On a network stream it is when the remote end signals it has finished sending.

finish (Writable)

Fires after writable.end() has been called and all data has been flushed to the underlying system (file, socket, etc). Not to be confused with end on a Readable — different event, different stream type.

drain (Writable)

Fires after writable.write(chunk) returned false (the buffer reached highWaterMark) and the buffer has since drained below the threshold. This is the backpressure resume signal. Do not write again until drain fires:

function writeChunk(writable, chunk) {
  if (!writable.write(chunk)) {
    writable.once('drain', () => writeChunk(writable, nextChunk));
  }
}

close (all)

Fires once the stream and any underlying OS resource (file descriptor, socket handle) have been released. No further events fire after close. With autoDestroy: true (the modern default), close follows end/finish or error automatically.

error (all)

An unhandled error event throws and can crash the Node process. Always attach an error handler:

stream.on('error', (err) => console.error('stream error:', err));

Or use stream.pipeline(), which propagates errors and calls destroy() on every stream in the chain on failure.

pipe / unpipe (Writable)

Fire when a Readable is piped into or unpiped from this Writable. Useful for tracking how many sources are writing to a given destination.

Backpressure: the critical pattern

The fundamental rule: if write() returns false, stop writing until drain fires. Ignoring this causes the internal buffer to grow without bound, eventually exhausting process memory.

stream.pipeline() (Node 10+) handles backpressure, errors, and cleanup automatically and is the recommended way to connect streams:

const { pipeline } = require('node:stream');
const { promisify } = require('node:util');
const pipelineAsync = promisify(pipeline);

await pipelineAsync(
  fs.createReadStream('source.txt'),
  zlib.createGzip(),
  fs.createWriteStream('dest.gz')
);

Event ordering on clean termination

Stream typeNormal event sequence
Readable (file)data × N → endclose
Writable (file)drain (if needed) → finishclose
Duplex (TCP)Read side: data × N → end; Write side: finish; then close

On error: the stream goes directly to close after emitting error, skipping end/finish.