Streams are a fundamental concept in Node.js that allow for efficient handling of data. They provide a way to handle reading and writing files, network communications, and any type of end-to-end information exchange.

What are streams?

Streams are not unique to Node.js and have been around for decades, with origins in the Unix operating system. In traditional data handling methods, when you read a file, the entire file is loaded into memory before it can be processed. Streams, on the other hand, allow you to read the file piece by piece and process its content without keeping it all in memory.

Why are streams important?

Streams offer two major advantages over other data handling methods: memory efficiency and time efficiency. With streams, you don’t need to load large amounts of data into memory before processing it, saving valuable resources. Additionally, you can start processing data as soon as you have it, rather than waiting for the entire data payload to be available.

An example of a stream

Let’s take a look at a typical example of reading files from a disk. In the traditional way, you would use the Node fs module’s readFile() function, which reads the entire contents of the file into memory before processing it. However, with streams, you can achieve the same result more efficiently by using the createReadStream() function and the pipe() method:

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

const server = http.createServer((req, res) => {
  const stream = fs.createReadStream(__dirname + '/data.txt');
  stream.pipe(res);
});
server.listen(3000);

In this example, instead of waiting until the file is fully read, the data is streamed to the HTTP client as soon as it is available.

Understanding the pipe() method

The pipe() method is used to take a source stream and pipe it into a destination stream. It allows for easy chaining of multiple pipe() calls. For example, src.pipe(dest1).pipe(dest2) is equivalent to src.pipe(dest1); dest1.pipe(dest2).

Streams-powered Node APIs

Many core modules in Node.js provide native stream handling capabilities. Some notable ones include:

  • process.stdin: A stream connected to stdin.
  • process.stdout: A stream connected to stdout.
  • process.stderr: A stream connected to stderr.
  • fs.createReadStream(): Creates a readable stream to a file.
  • fs.createWriteStream(): Creates a writable stream to a file.
  • net.connect(): Initiates a stream-based connection.
  • http.request(): Returns an instance of the http.ClientRequest class, which is a writable stream.
  • zlib.createGzip(): Compresses data using Gzip into a stream.
  • zlib.createGunzip(): Decompresses a Gzip stream.
  • zlib.createDeflate(): Compresses data using Deflate into a stream.
  • zlib.createInflate(): Decompresses a Deflate stream.

Different types of streams

There are four classes of streams:

  • Readable: A stream you can receive data from but not send data to it.
  • Writable: A stream you can send data to but not receive data from it.
  • Duplex: A stream you can both send and receive data from.
  • Transform: A stream that transforms its input into its output.

How to create a readable stream

To create a readable stream, use the Readable object from the stream module and initialize it. Then, you can use the push() method to send data to it.

How to create a writable stream

To create a writable stream, extend the base Writable object and implement its _write() method. After creating the stream object, you can use the pipe() method to pipe a readable stream into it.

How to get data from a readable stream

To read data from a readable stream, you can either pipe it into a writable stream or consume it directly using the readable event.

How to send data to a writable stream

Data can be sent to a writable stream using the write() method.

Signaling the end of writing to a writable stream

To signal the end of writing to a writable stream, you can use the end() method.

Conclusion

Streams are a powerful feature in Node.js that offer memory and time efficiency when handling data. They provide a way to read and write data in a more efficient manner, making them an essential tool for building high-performance Node.js applications.