readable.pipe()
Connect a readable stream to a writable one with automatic backpressure.
readable.pipe(destination[, options])- Added in Node.js v0.9.4
Parameters
| Parameter | Type | Description |
|---|---|---|
$destination | stream.Writable | The destination for writing data. |
$optionsoptional | Object | Pipe options. |
$options.endoptional | boolean= true | End the writer when the reader ends. Set to false to keep the destination open for further writes. |
Return value
stream.WritableThe destination, allowing a chain of pipes if it is a Duplex or a Transform stream.
Errors
Does not throw. Errors on either stream are emitted on that stream only and are not propagated across the pipe, so an unhandled 'error' becomes an uncaught exception.
pipe() attaches a writable to a readable, switches the readable into flowing mode, and manages the flow so a fast source cannot overwhelm a slow destination. It returns the destination, which is what makes a.pipe(b).pipe(c) read left to right when the middle stages are Transforms.
When to reach for it
For a single hop where you already handle errors — or in a REPL, a script, or an example where brevity matters more than robustness. In production code the honest answer is: reach for pipeline() instead.
What trips people up
pipe() does not forward errors. If the source fails halfway through, the destination is never told; it stays open, its file descriptor leaks, and the source's 'error' event — having no listener — crashes the process. Handling this correctly means attaching an 'error' listener to every stream in the chain and destroying the others by hand. That is exactly the boilerplate pipeline() exists to delete.
The second surprise is that pipe() ends the destination by default, so a stream you piped into once cannot be piped into again unless you pass { end: false }. And because it returns the destination rather than the source, readable.pipe(writable).on('end', ...) waits for an event a writable never emits — you want 'finish'.
Example
When to use it
- Copy or compress a file in a short script where a crash is acceptable
- Send an HTTP response body straight to disk in a one-off tool
- Fan one source into several destinations that each stay open
- Wire a Transform between two streams in a REPL experiment
Frequently asked questions
Does readable.pipe() handle errors in Node.js?
No — pipe() forwards data but not errors, so a failure on one stream leaves the others open and its 'error' event unhandled. Use stream.pipeline() instead, which forwards the error and destroys every stream in the chain.
What does readable.pipe() return?
It returns the destination stream, not the source, which is what allows chaining like a.pipe(b).pipe(c). It also means you should listen for 'finish' on the result rather than 'end'.
How do I stop pipe() from closing the destination?
Pass { end: false } as the second argument, then call destination.end() yourself when you are done. This is how you pipe several sources into one writable.
More examples
What error handling with pipe actually costs
This is the minimum correct error handling for a two-hop pipe chain, and it is still subtly incomplete — destroy() on an already-destroyed stream, and the ordering of 'close' events, are both easy to get wrong. The commented pipeline() call at the bottom does all of it, which is why pipe() is discouraged for chains you care about.
const fs = require('node:fs');
const { createGzip } = require('node:zlib');
const source = fs.createReadStream('big.log');
const gzip = createGzip();
const dest = fs.createWriteStream('big.log.gz');
// Every stream needs its own handler, and each must tear down the others.
function fail(err) {
source.destroy();
gzip.destroy();
dest.destroy();
console.error('failed:', err.message);
}
source.on('error', fail);
gzip.on('error', fail);
dest.on('error', fail);
dest.on('finish', () => console.log('compressed'));
source.pipe(gzip).pipe(dest);
// The equivalent, correct in three lines:
// const { pipeline } = require('node:stream/promises');
// await pipeline(source, gzip, dest);Keeping the destination open with end: false
Concatenates two sources into one destination. The default end: true would close `out` as soon as the first source finished, so every subsequent write would fail; passing end: false makes closing the destination your responsibility, done explicitly in the last line.
const { Readable, PassThrough } = require('node:stream');
const out = new PassThrough({ encoding: 'utf8' });
out.on('data', (chunk) => console.log('got:', chunk));
const first = Readable.from(['a', 'b'], { objectMode: false });
const second = Readable.from(['c', 'd'], { objectMode: false });
// Without end: false, the first source would close `out`
// and the second pipe would throw ERR_STREAM_WRITE_AFTER_END.
first.pipe(out, { end: false });
first.on('end', () => {
second.pipe(out, { end: false });
second.on('end', () => out.end());
});
Discussion