Node.js Streams Explained: Processing Large Data Without Running Out of Memory
Streams let Node.js process data piece by piece instead of loading it all into memory at once — which is the only sane way to handle large files, big HTTP responses, or continuous data. If you have ever crashed a process reading a huge file, streams are the fix.
Why streams matter
Reading a 2 GB file with fs.readFile tries to hold all 2 GB in memory — and may crash with an out-of-memory error. A stream reads it in small chunks, using a fraction of the memory, and starts working immediately rather than waiting for the whole file.
The four stream types
- Readable — you read data from it (a file being read, an HTTP request body).
- Writable — you write data to it (a file being written, an HTTP response).
- Duplex — both readable and writable (a TCP socket).
- Transform — a duplex stream that modifies data passing through (compression, encryption).
Piping: the elegant part
The pipe() method connects a readable stream to a writable one and handles the flow for you:
fs.createReadStream('big.csv').pipe(transform).pipe(fs.createWriteStream('out.csv'));
Data flows through in chunks, memory stays low, and piping even manages backpressure automatically. Modern code often uses stream.pipeline() for better error handling.
Where you will use streams
- Serving or processing large file downloads and uploads.
- Reading huge CSV or log files line by line.
- Compressing or transforming data on the fly.
Frequently asked questions
When should I use a stream instead of reading the whole file?
Any time the data is large or its size is unknown. Streams keep memory flat regardless of size, which prevents crashes and keeps your app responsive under load.
What is a Transform stream good for?
Modifying data as it flows — gzip compression, encryption, CSV parsing. You read raw data in one end and get transformed data out the other, all without buffering the whole thing.
Why use pipeline() over pipe()?
stream.pipeline() propagates errors and cleans up all streams properly if one fails, which plain pipe() does not. For production code, pipeline is the safer choice.
Was this article helpful?