Welcome back to Node.js Streaming 101! 🚀
If you haven’t checked out Part 1, I highly recommend reading it first—especially if you're new to streams in Node.js.
Today, we’re focusing on Duplex Streams, the true multi-taskers of the Node.js streaming world.
🔄 What Are Duplex Streams?
A Duplex Stream is a special type of stream that can be both readable and writable at the same time. Think of it like a walkie-talkie—you can send and receive data simultaneously.
Where Do We Use Duplex Streams?
Duplex Streams are everywhere:
- Network sockets (TCP/WebSockets) 📡 – Sending and receiving data.
- Data relay systems 🔄 – Processing and forwarding data in real time.
- Serial port communication ⚡ – Interacting with hardware devices.
🏗 Creating a Simple Duplex Stream
Let’s create a custom Duplex Stream that buffers incoming data and allows it to be read later.
import { Duplex } from "stream";
class CustomDuplex extends Duplex {
  constructor(options) {
    super(options);
    this.data = []; // Temporary storage
  }
  _write(chunk, encoding, callback) {
    console.log(`Writing: ${chunk.toString()}`);
    this.data.push(chunk); // Store received data
    callback();
  }
  _read(size) {
    if (this.data.length > 0) {
      this.push(this.data.shift()); // Provide stored data when read is requested
    } else {
      this.push(null); // Signal end of stream
    }
  }
}
const duplexStream = new CustomDuplex();
duplexStream.write("Hello, ");
duplexStream.write("world!");
duplexStream.end(); // Signals that writing is complete
duplexStream.on("data", (chunk) => {
  console.log(`Reading: ${chunk.toString()}`);
});🔍 What’s Happening?
- We define a CustomDuplexclass extendingDuplex.
- _write(chunk, encoding, callback): Stores incoming data.
- _read(size): Reads the stored data when requested.
- We write to the stream (write("Hello, ")), and later read from it (on("data")).
đź§Ş Expected Output
Writing: Hello,
Writing: world!
Reading: Hello,
Reading: world!Boom! 🚀 You just built your first Duplex Stream!
⚡ Duplex Streams in Action: A TCP Echo Server
A real-world example of Duplex Streams is a TCP Echo Server, which receives data and immediately sends it back to the sender.
import { createServer } from "net";
const server = createServer((socket) => {
  console.log("Client connected");
  socket.on("data", (chunk) => {
    console.log(`Received: ${chunk.toString()}`);
    socket.write(`Echo: ${chunk.toString()}`); // Send data back
  });
  socket.on("end", () => {
    console.log("Client disconnected");
  });
});
server.listen(4000, () => {
  console.log("Echo server listening on port 4000...");
});đź§Ş Test It!
- Run this script (node server.mjs).
- Open another terminal and use Netcat (nc):nc localhost 4000
- Type something (e.g., "Hello!") and hit Enter.
- The server echoes back "Echo: Hello!".
You just built a Duplex Stream-powered network application! 🚀
đź”— Using Duplex Streams with pipeline()
Let’s modify our Duplex Stream to work with pipeline() for better error handling.
import { Duplex, pipeline } from "stream/promises";
class EchoStream extends Duplex {
  _write(chunk, encoding, callback) {
    console.log(`Received: ${chunk.toString()}`);
    this.push(`Echo: ${chunk.toString()}`); // Push back modified data
    callback();
  }
  _read() {} // No need to implement, handled by push()
}
async function processDuplexStream() {
  const duplex = new EchoStream();
  try {
    await pipeline(
      process.stdin, // Input from terminal
      duplex, // Our custom Duplex Stream
      process.stdout // Output to terminal
    );
  } catch (err) {
    console.error("Pipeline failed:", err);
  }
}
processDuplexStream();đź§Ş How to Use It
- Run this script (node duplex-pipeline.mjs).
- Type any text and press Enter.
- It echoes the text back.
Hello world
Echo: Hello world🚀 Final Thoughts
Duplex Streams are incredibly powerful when you need bi-directional data flow.
Whether you’re handling network sockets, file forwarding, or real-time processing, they make data handling in Node.js fast, efficient, and flexible.