API for stream implementers
History
The node:stream module API has been designed to make it possible to easily
implement streams using JavaScript's prototypal inheritance model.
First, a stream developer would declare a new JavaScript class that extends one
of the four basic stream classes (stream.Writable, stream.Readable,
stream.Duplex, or stream.Transform), making sure they call the appropriate
parent class constructor:
const { Writable } = require('node:stream'); class MyWritable extends Writable { constructor({ highWaterMark, ...options }) { super({ highWaterMark }); // ... } }
When extending streams, keep in mind what options the user
can and should provide before forwarding these to the base constructor. For
example, if the implementation makes assumptions in regard to the
autoDestroy and emitClose options, do not allow the
user to override these. Be explicit about what
options are forwarded instead of implicitly forwarding all options.
The new stream class must then implement one or more specific methods, depending on the type of stream being created, as detailed in the chart below:
| Use-case | Class | Method(s) to implement |
|---|---|---|
| Reading only | Readable | _read() |
| Writing only | Writable | _write(), _writev(), _final() |
| Reading and writing | Duplex | _read(), _write(), _writev(), _final() |
| Operate on written data, then read the result | Transform | _transform(), _flush(), _final() |
The implementation code for a stream should never call the "public" methods of a stream that are intended for use by consumers (as described in the API for stream consumers section). Doing so may lead to adverse side effects in application code consuming the stream.
Avoid overriding public methods such as write(), end(), cork(),
uncork(), read() and destroy(), or emitting internal events such
as 'error', 'data', 'end', 'finish' and 'close' through .emit().
Doing so can break current and future stream invariants leading to behavior
and/or compatibility issues with other streams, stream utilities, and user
expectations.
Simplified construction
History
For many simple cases, it is possible to create a stream without relying on
inheritance. This can be accomplished by directly creating instances of the
stream.Writable, stream.Readable, stream.Duplex, or stream.Transform
objects and passing appropriate methods as constructor options.
const { Writable } = require('node:stream'); const myWritable = new Writable({ construct(callback) { // Initialize state and load resources... }, write(chunk, encoding, callback) { // ... }, destroy() { // Free resources... }, });
The stream.Writable class is extended to implement a Writable stream.
Custom Writable streams must call the new stream.Writable([options])
constructor and implement the writable._write() and/or writable._writev()
method.
stream.Writable Constructor
History
autoDestroy option default to true.autoDestroy option to automatically destroy() the stream when it emits 'finish' or errors.emitClose option to specify if 'close' is emitted on destroy.new stream.Writable(options?): stream.Writable
Objectnumberstream.write() starts returning false. Default:
See stream.getDefaultHighWaterMark().booleanstrings passed to
stream.write() to Buffers (with the encoding
specified in the stream.write() call) before passing
them to stream._write(). Other types of data are not
converted (i.e. Buffers are not decoded into strings). Setting to
false will prevent strings from being converted. Default: true.stringstream.write().
Default: 'utf8'.booleanstream.write(anyObj) is a valid operation. When set,
it becomes possible to write JavaScript values other than string, Buffer,
TypedArray or DataView if supported by the stream implementation.
Default: false.boolean'close'
after it has been destroyed. Default: true.Functionstream._write() method.Functionstream._writev() method.Functionstream._destroy() method.Functionstream._final() method.Functionstream._construct() method.boolean.destroy() on itself after ending. Default: true.AbortSignalconst { Writable } = require('node:stream'); class MyWritable extends Writable { constructor(options) { // Calls the stream.Writable() constructor. super(options); // ... } }
import { Writable } from 'node:stream'; class MyWritable extends Writable { constructor(options) { // Calls the stream.Writable() constructor. super(options); // ... } }
Or, using the simplified constructor approach:
const { Writable } = require('node:stream'); const myWritable = new Writable({ write(chunk, encoding, callback) { // ... }, writev(chunks, callback) { // ... }, });
Calling abort on the AbortController corresponding to the passed
AbortSignal will behave the same way as calling .destroy(new AbortError())
on the writable stream.
const { Writable } = require('node:stream'); const controller = new AbortController(); const myWritable = new Writable({ write(chunk, encoding, callback) { // ... }, writev(chunks, callback) { // ... }, signal: controller.signal, }); // Later, abort the operation closing the stream controller.abort();
writable._construct(callback): void
FunctionThe _construct() method MUST NOT be called directly. It may be implemented
by child classes, and if so, will be called by the internal Writable
class methods only.
This optional function will be called in a tick after the stream constructor
has returned, delaying any _write(), _final() and _destroy() calls until
callback is called. This is useful to initialize state or asynchronously
initialize resources before the stream can be used.
const { Writable } = require('node:stream'); const fs = require('node:fs'); class WriteStream extends Writable { constructor(filename) { super(); this.filename = filename; this.fd = null; } _construct(callback) { fs.open(this.filename, 'w', (err, fd) => { if (err) { callback(err); } else { this.fd = fd; callback(); } }); } _write(chunk, encoding, callback) { fs.write(this.fd, chunk, callback); } _destroy(err, callback) { if (this.fd) { fs.close(this.fd, (er) => callback(er || err)); } else { callback(err); } } }
writable._write(chunk, encoding, callback): void
Buffer to be written, converted from the
string passed to stream.write(). If the stream's
decodeStrings option is false or the stream is operating in object mode,
the chunk will not be converted & will be whatever was passed to
stream.write().stringencoding is the
character encoding of that string. If chunk is a Buffer, or if the
stream is operating in object mode, encoding may be ignored.FunctionAll Writable stream implementations must provide a
writable._write() and/or
writable._writev() method to send data to the underlying
resource.
Transform streams provide their own implementation of the
writable._write().
This function MUST NOT be called by application code directly. It should be
implemented by child classes, and called by the internal Writable class
methods only.
The callback function must be called synchronously inside of
writable._write() or asynchronously (i.e. different tick) to signal either
that the write completed successfully or failed with an error.
The first argument passed to the callback must be the Error object if the
call failed or null if the write succeeded.
All calls to writable.write() that occur between the time writable._write()
is called and the callback is called will cause the written data to be
buffered. When the callback is invoked, the stream might emit a 'drain'
event. If a stream implementation is capable of processing multiple chunks of
data at once, the writable._writev() method should be implemented.
If the decodeStrings property is explicitly set to false in the constructor
options, then chunk will remain the same object that is passed to .write(),
and may be a string rather than a Buffer. This is to support implementations
that have an optimized handling for certain string data encodings. In that case,
the encoding argument will indicate the character encoding of the string.
Otherwise, the encoding argument can be safely ignored.
The writable._write() method is prefixed with an underscore because it is
internal to the class that defines it, and should never be called directly by
user programs.
writable._writev(chunks, callback): void
Object[]Object
that each represent a discrete chunk of data to write. The properties of
these objects are:chunk will be a string if the Writable was created with
the decodeStrings option set to false and a string was passed to write().stringchunk. If chunk is
a Buffer, the encoding will be 'buffer'.FunctionThis function MUST NOT be called by application code directly. It should be
implemented by child classes, and called by the internal Writable class
methods only.
The writable._writev() method may be implemented in addition or alternatively
to writable._write() in stream implementations that are capable of processing
multiple chunks of data at once. If implemented and if there is buffered data
from previous writes, _writev() will be called instead of _write().
The writable._writev() method is prefixed with an underscore because it is
internal to the class that defines it, and should never be called directly by
user programs.
writable._destroy(err, callback): void
The _destroy() method is called by writable.destroy().
It can be overridden by child classes but it must not be called directly.
writable._final(callback): void
FunctionThe _final() method must not be called directly. It may be implemented
by child classes, and if so, will be called by the internal Writable
class methods only.
This optional function will be called before the stream closes, delaying the
'finish' event until callback is called. This is useful to close resources
or write buffered data before a stream ends.
Errors occurring during the processing of the writable._write(),
writable._writev() and writable._final() methods must be propagated
by invoking the callback and passing the error as the first argument.
Throwing an Error from within these methods or manually emitting an 'error'
event results in undefined behavior.
If a Readable stream pipes into a Writable stream when Writable emits an
error, the Readable stream will be unpiped.
const { Writable } = require('node:stream'); const myWritable = new Writable({ write(chunk, encoding, callback) { if (chunk.toString().indexOf('a') >= 0) { callback(new Error('chunk is invalid')); } else { callback(); } }, });
The following illustrates a rather simplistic (and somewhat pointless) custom
Writable stream implementation. While this specific Writable stream instance
is not of any real particular usefulness, the example illustrates each of the
required elements of a custom Writable stream instance:
const { Writable } = require('node:stream'); class MyWritable extends Writable { _write(chunk, encoding, callback) { if (chunk.toString().indexOf('a') >= 0) { callback(new Error('chunk is invalid')); } else { callback(); } } }
Decoding buffers is a common task, for instance, when using transformers whose
input is a string. This is not a trivial process when using multi-byte
characters encoding, such as UTF-8. The following example shows how to decode
multi-byte strings using StringDecoder and Writable.
const { Writable } = require('node:stream'); const { StringDecoder } = require('node:string_decoder'); class StringWritable extends Writable { constructor(options) { super(options); this._decoder = new StringDecoder(options?.defaultEncoding); this.data = ''; } _write(chunk, encoding, callback) { if (encoding === 'buffer') { chunk = this._decoder.write(chunk); } this.data += chunk; callback(); } _final(callback) { this.data += this._decoder.end(); callback(); } } const euro = [[0xE2, 0x82], [0xAC]].map(Buffer.from); const w = new StringWritable(); w.write('currency: '); w.write(euro[0]); w.end(euro[1]); console.log(w.data); // currency: €
The stream.Readable class is extended to implement a Readable stream.
Custom Readable streams must call the new stream.Readable([options])
constructor and implement the readable._read() method.
new stream.Readable(options?): stream.Readable
Objectnumberstream.getDefaultHighWaterMark().stringnull.booleanstream.read(n) returns
a single value instead of a Buffer of size n. Default: false.boolean'close'
after it has been destroyed. Default: true.Functionstream._read()
method.Functionstream._destroy() method.Functionstream._construct() method.boolean.destroy() on itself after ending. Default: true.AbortSignalconst { Readable } = require('node:stream'); class MyReadable extends Readable { constructor(options) { // Calls the stream.Readable(options) constructor. super(options); // ... } }
Or, using the simplified constructor approach:
const { Readable } = require('node:stream'); const myReadable = new Readable({ read(size) { // ... }, });
Calling abort on the AbortController corresponding to the passed
AbortSignal will behave the same way as calling .destroy(new AbortError())
on the readable created.
const { Readable } = require('node:stream'); const controller = new AbortController(); const read = new Readable({ read(size) { // ... }, signal: controller.signal, }); // Later, abort the operation closing the stream controller.abort();
readable._construct(callback): void
FunctionThe _construct() method MUST NOT be called directly. It may be implemented
by child classes, and if so, will be called by the internal Readable
class methods only.
This optional function will be scheduled in the next tick by the stream
constructor, delaying any _read() and _destroy() calls until callback is
called. This is useful to initialize state or asynchronously initialize
resources before the stream can be used.
const { Readable } = require('node:stream'); const fs = require('node:fs'); class ReadStream extends Readable { constructor(filename) { super(); this.filename = filename; this.fd = null; } _construct(callback) { fs.open(this.filename, (err, fd) => { if (err) { callback(err); } else { this.fd = fd; callback(); } }); } _read(n) { const buf = Buffer.alloc(n); fs.read(this.fd, buf, 0, n, null, (err, bytesRead) => { if (err) { this.destroy(err); } else { this.push(bytesRead > 0 ? buf.slice(0, bytesRead) : null); } }); } _destroy(err, callback) { if (this.fd) { fs.close(this.fd, (er) => callback(er || err)); } else { callback(err); } } }
readable._read(size): void
numberThis function MUST NOT be called by application code directly. It should be
implemented by child classes, and called by the internal Readable class
methods only.
All Readable stream implementations must provide an implementation of the
readable._read() method to fetch data from the underlying resource.
When readable._read() is called, if data is available from the resource,
the implementation should begin pushing that data into the read queue using the
this.push(dataChunk) method. _read() will be called again
after each call to this.push(dataChunk) once the stream is
ready to accept more data. _read() may continue reading from the resource and
pushing data until readable.push() returns false. Only when _read() is
called again after it has stopped should it resume pushing additional data into
the queue.
Once the readable._read() method has been called, it will not be called
again until more data is pushed through the readable.push()
method. Empty data such as empty buffers and strings will not cause
readable._read() to be called.
The size argument is advisory. For implementations where a "read" is a
single operation that returns data can use the size argument to determine how
much data to fetch. Other implementations may ignore this argument and simply
provide data whenever it becomes available. There is no need to "wait" until
size bytes are available before calling stream.push(chunk).
The readable._read() method is prefixed with an underscore because it is
internal to the class that defines it, and should never be called directly by
user programs.
readable._destroy(err, callback): void
The _destroy() method is called by readable.destroy().
It can be overridden by child classes but it must not be called directly.
readable.push(chunk, encoding?): boolean
chunk must
be a string, Buffer, TypedArray or DataView. For object mode streams,
chunk may be any JavaScript value.stringBuffer encoding, such as 'utf8' or 'ascii'.booleantrue if additional chunks of data may continue to be
pushed; false otherwise.When chunk is a Buffer, TypedArray, DataView or string, the chunk
of data will be added to the internal queue for users of the stream to consume.
Passing chunk as null signals the end of the stream (EOF), after which no
more data can be written.
When the Readable is operating in paused mode, the data added with
readable.push() can be read out by calling the
readable.read() method when the 'readable' event is
emitted.
When the Readable is operating in flowing mode, the data added with
readable.push() will be delivered by emitting a 'data' event.
The readable.push() method is designed to be as flexible as possible. For
example, when wrapping a lower-level source that provides some form of
pause/resume mechanism, and a data callback, the low-level source can be wrapped
by the custom Readable instance:
// `_source` is an object with readStop() and readStart() methods, // and an `ondata` member that gets called when it has data, and // an `onend` member that gets called when the data is over. class SourceWrapper extends Readable { constructor(options) { super(options); this._source = getLowLevelSourceObject(); // Every time there's data, push it into the internal buffer. this._source.ondata = (chunk) => { // If push() returns false, then stop reading from source. if (!this.push(chunk)) this._source.readStop(); }; // When the source ends, push the EOF-signaling `null` chunk. this._source.onend = () => { this.push(null); }; } // _read() will be called when the stream wants to pull more data in. // The advisory size argument is ignored in this case. _read(size) { this._source.readStart(); } }
The readable.push() method is used to push the content
into the internal buffer. It can be driven by the readable._read() method.
For streams not operating in object mode, if the chunk parameter of
readable.push() is undefined, it will be treated as empty string or
buffer. See readable.push('') for more information.
Errors occurring during processing of the readable._read() must be
propagated through the readable.destroy(err) method.
Throwing an Error from within readable._read() or manually emitting an
'error' event results in undefined behavior.
const { Readable } = require('node:stream'); const myReadable = new Readable({ read(size) { const err = checkSomeErrorCondition(); if (err) { this.destroy(err); } else { // Do some work. } }, });
The following is a basic example of a Readable stream that emits the numerals
from 1 to 1,000,000 in ascending order, and then ends.
const { Readable } = require('node:stream'); class Counter extends Readable { constructor(opt) { super(opt); this._max = 1000000; this._index = 1; } _read() { const i = this._index++; if (i > this._max) this.push(null); else { const str = String(i); const buf = Buffer.from(str, 'ascii'); this.push(buf); } } }
A Duplex stream is one that implements both Readable and
Writable, such as a TCP socket connection.
Because JavaScript does not have support for multiple inheritance, the
stream.Duplex class is extended to implement a Duplex stream (as opposed
to extending the stream.Readable and stream.Writable classes).
The stream.Duplex class prototypically inherits from stream.Readable and
parasitically from stream.Writable, but instanceof will work properly for
both base classes due to overriding Symbol.hasInstance on
stream.Writable.
Custom Duplex streams must call the new stream.Duplex([options])
constructor and implement both the readable._read() and
writable._write() methods.
new stream.Duplex(options): stream.Duplex
ObjectWritable and Readable
constructors. Also has the following fields:booleanfalse, then the stream will
automatically end the writable side when the readable side ends.
Default: true.booleanDuplex should be readable.
Default: true.booleanDuplex should be writable.
Default: true.booleanobjectMode for readable side of the
stream. Has no effect if objectMode is true. Default: false.booleanobjectMode for writable side of the
stream. Has no effect if objectMode is true. Default: false.numberhighWaterMark for the readable side
of the stream. Has no effect if highWaterMark is provided.numberhighWaterMark for the writable side
of the stream. Has no effect if highWaterMark is provided.const { Duplex } = require('node:stream'); class MyDuplex extends Duplex { constructor(options) { super(options); // ... } }
import { Duplex } from 'node:stream'; class MyDuplex extends Duplex { constructor(options) { super(options); // ... } }
Or, using the simplified constructor approach:
const { Duplex } = require('node:stream'); const myDuplex = new Duplex({ read(size) { // ... }, write(chunk, encoding, callback) { // ... }, });
When using pipeline:
const { Transform, pipeline } = require('node:stream'); const fs = require('node:fs'); pipeline( fs.createReadStream('object.json') .setEncoding('utf8'), new Transform({ decodeStrings: false, // Accept string input rather than Buffers construct(callback) { this.data = ''; callback(); }, transform(chunk, encoding, callback) { this.data += chunk; callback(); }, flush(callback) { try { // Make sure is valid json. JSON.parse(this.data); this.push(this.data); callback(); } catch (err) { callback(err); } }, }), fs.createWriteStream('valid-object.json'), (err) => { if (err) { console.error('failed', err); } else { console.log('completed'); } }, );
The following illustrates a simple example of a Duplex stream that wraps a
hypothetical lower-level source object to which data can be written, and
from which data can be read, albeit using an API that is not compatible with
Node.js streams.
The following illustrates a simple example of a Duplex stream that buffers
incoming written data via the Writable interface that is read back out
via the Readable interface.
const { Duplex } = require('node:stream'); const kSource = Symbol('source'); class MyDuplex extends Duplex { constructor(source, options) { super(options); this[kSource] = source; } _write(chunk, encoding, callback) { // The underlying source only deals with strings. if (Buffer.isBuffer(chunk)) chunk = chunk.toString(); this[kSource].writeSomeData(chunk); callback(); } _read(size) { this[kSource].fetchSomeData(size, (data, encoding) => { this.push(Buffer.from(data, encoding)); }); } }
The most important aspect of a Duplex stream is that the Readable and
Writable sides operate independently of one another despite co-existing within
a single object instance.
For Duplex streams, objectMode can be set exclusively for either the
Readable or Writable side using the readableObjectMode and
writableObjectMode options respectively.
In the following example, for instance, a new Transform stream (which is a
type of Duplex stream) is created that has an object mode Writable side
that accepts JavaScript numbers that are converted to hexadecimal strings on
the Readable side.
const { Transform } = require('node:stream'); // All Transform streams are also Duplex Streams. const myTransform = new Transform({ writableObjectMode: true, transform(chunk, encoding, callback) { // Coerce the chunk to a number if necessary. chunk |= 0; // Transform the chunk into something else. const data = chunk.toString(16); // Push the data onto the readable queue. callback(null, '0'.repeat(data.length % 2) + data); }, }); myTransform.setEncoding('ascii'); myTransform.on('data', (chunk) => console.log(chunk)); myTransform.write(1); // Prints: 01 myTransform.write(10); // Prints: 0a myTransform.write(100); // Prints: 64
A Transform stream is a Duplex stream where the output is computed
in some way from the input. Examples include zlib streams or crypto
streams that compress, encrypt, or decrypt data.
There is no requirement that the output be the same size as the input, the same
number of chunks, or arrive at the same time. For example, a Hash stream will
only ever have a single chunk of output which is provided when the input is
ended. A zlib stream will produce output that is either much smaller or much
larger than its input.
The stream.Transform class is extended to implement a Transform stream.
The stream.Transform class prototypically inherits from stream.Duplex and
implements its own versions of the writable._write() and
readable._read() methods. Custom Transform implementations must
implement the transform._transform() method and may
also implement the transform._flush() method.
Care must be taken when using Transform streams in that data written to the
stream can cause the Writable side of the stream to become paused if the
output on the Readable side is not consumed.
new stream.Transform(options?): stream.Transform
ObjectWritable and Readable
constructors. Also has the following fields:Functionstream._transform() method.Functionstream._flush()
method.const { Transform } = require('node:stream'); class MyTransform extends Transform { constructor(options) { super(options); // ... } }
import { Transform } from 'node:stream'; class MyTransform extends Transform { constructor(options) { super(options); // ... } }
Or, using the simplified constructor approach:
const { Transform } = require('node:stream'); const myTransform = new Transform({ transform(chunk, encoding, callback) { // ... }, });
end
The 'end' event is from the stream.Readable class. The 'end' event is
emitted after all data has been output, which occurs after the callback in
transform._flush() has been called. In the case of an error,
'end' should not be emitted.
The 'finish' event is from the stream.Writable class. The 'finish'
event is emitted after stream.end() is called and all chunks
have been processed by stream._transform(). In the case
of an error, 'finish' should not be emitted.
transform._flush(callback): void
FunctionThis function MUST NOT be called by application code directly. It should be
implemented by child classes, and called by the internal Readable class
methods only.
In some cases, a transform operation may need to emit an additional bit of
data at the end of the stream. For example, a zlib compression stream will
store an amount of internal state used to optimally compress the output. When
the stream ends, however, that additional data needs to be flushed so that the
compressed data will be complete.
Custom Transform implementations may implement the transform._flush()
method. This will be called when there is no more written data to be consumed,
but before the 'end' event is emitted signaling the end of the
Readable stream.
Within the transform._flush() implementation, the transform.push() method
may be called zero or more times, as appropriate. The callback function must
be called when the flush operation is complete.
The transform._flush() method is prefixed with an underscore because it is
internal to the class that defines it, and should never be called directly by
user programs.
transform._transform(chunk, encoding, callback): void
Buffer to be transformed, converted from
the string passed to stream.write(). If the stream's
decodeStrings option is false or the stream is operating in object mode,
the chunk will not be converted & will be whatever was passed to
stream.write().string'buffer'. Ignore it in that case.Functionchunk has been
processed.This function MUST NOT be called by application code directly. It should be
implemented by child classes, and called by the internal Readable class
methods only.
All Transform stream implementations must provide a _transform()
method to accept input and produce output. The transform._transform()
implementation handles the bytes being written, computes an output, then passes
that output off to the readable portion using the transform.push() method.
The transform.push() method may be called zero or more times to generate
output from a single input chunk, depending on how much is to be output
as a result of the chunk.
It is possible that no output is generated from any given chunk of input data.
The callback function must be called only when the current chunk is completely
consumed. The first argument passed to the callback must be an Error object
if an error occurred while processing the input or null otherwise. If a second
argument is passed to the callback, it will be forwarded on to the
transform.push() method, but only if the first argument is falsy. In other
words, the following are equivalent:
transform.prototype._transform = function(data, encoding, callback) { this.push(data); callback(); }; transform.prototype._transform = function(data, encoding, callback) { callback(null, data); };
The transform._transform() method is prefixed with an underscore because it
is internal to the class that defines it, and should never be called directly by
user programs.
transform._transform() is never called in parallel; streams implement a
queue mechanism, and to receive the next chunk, callback must be
called, either synchronously or asynchronously.
The stream.PassThrough class is a trivial implementation of a Transform
stream that simply passes the input bytes across to the output. Its purpose is
primarily for examples and testing, but there are some use cases where
stream.PassThrough is useful as a building block for novel sorts of streams.