Node interprocess communication

The question on efficient Node.js inter-process communication (IPC) methods has garnered several thoughtful answers, each recommending different approaches depending on the use case.

Here sharing key options and advantages and disadvantages of some methods

1. Redis Pub/Sub

Overview: Redis Pub/Sub enables message communication between processes, even across machines, by using channels.
Code Example:

const redis = require('redis');

async function main() {
  // Subscriber client
  const subscriber = redis.createClient();
  const publisher = redis.createClient();
  // Handle errors
  subscriber.on('error', (err) => {
    console.error('Subscriber Client Error:', err);
  });
  publisher.on('error', (err) => {
    console.error('Publisher Client Error:', err);
  });
  // Connect both clients
  await subscriber.connect();
  await publisher.connect();
  console.log('Connected to Redis.');
  // Subscribe to the channel
  await subscriber.subscribe('my_channel', (message) => {
    console.log(`Received message: ${message}`);
  });

   // Publish a message to the channel
   await publisher.publish('my_channel', 'Hi…');
   console.log('Message published.');
}

// Run the main function
main().catch(console.error);

Advantages:

  1. Simple to implement.
  2. Fast and reliable for “send and forget” scenarios.
  3. Supports horizontal scaling.

Disadvantages:

  1. Not ideal for bi-directional communication without additional setup.
  2. There are some additional tasks of managing a Redis server.

2. Node.js IPC with child_process and cluster Modules

Overview: Node.js’s built-in modules (child_process and cluster) allow communication between parent and child processes or worker clusters.
Code Example:

// main.js
const { fork } = require('child_process');
const forked = fork('child.js');
forked.send({ msg: 'Hi...' });
forked.on('message', (msg) => {
  console.log('Message from child', msg);
});

//child.js
process.on('message', (msg) => {
    console.log('Message from parent:', msg);
  });
process.send({ msg: 'Hello....'  });

Advantages:

  1. No external dependencies.
  2. Works well for local machine communication.

Disadvantages:

  1. Does not natively support communication across different machines.
  2. Poor scalability.

3. ZeroMQ

Overview: ZeroMQ is a high-performance messaging library that supports patterns like pub/sub, request/reply
Code Example:

// Server.js
const zmq = require('zeromq');
async function runServer() {
  const sock = new zmq.Reply(); // Reply socket for server
  await sock.bind('tcp://127.0.0.1:3000'); // Bind the server to a TCP port
  console.log('Server is listening on tcp://127.0.0.1:3000');

  while (true) {
    const [message] = await sock.receive(); // Receive a message
    console.log('Received:', message.toString());

    // Reply to the client
    await sock.send(`Message received: ${message}`);
    console.log('Reply sent');
  }
}
runServer().catch((err) => console.error('Server Error:', err));
// client.js
const zmq = require('zeromq');
async function runClient() {
  const sock = new zmq.Request(); // Request socket for client
  sock.connect('tcp://127.0.0.1:3000'); // Connect to the server
  console.log('Client connected to server on tcp://127.0.0.1:3000');
  // Send a message to the server
  const message = 'Hello from Server!';
  await sock.send(message);
  console.log('Sent:', message);

  // Receive the server's reply
  const [reply] = await sock.receive();
  console.log('Received reply:', reply.toString());
}
runClient().catch((err) => console.error('Client Error:', err));

Advantages:

  1. Broker-less communication with various messaging patterns.
  2. Cross-language compatibility.

Disadvantages:

  1. Requires native bindings, which can complicate setup.

4. Node-IPC

Overview:A library for local and remote IPC using sockets (TCP, Unix/Windows).
Code Example:

//client.js
const IPC = require('node-ipc').default;

IPC.config.id = 'client'; // Unique ID for the client
IPC.config.retry = 1500;  // Retry interval for connections
IPC.config.silent = true; // Suppress console logs

IPC.connectTo('server', () => {
  IPC.of.server.on('connect', () => {
    console.log('Connected to server');
    // Send a message to the server
    IPC.of.server.emit('message', 'Hello Server!');
    console.log('Message sent to server');
  });

  IPC.of.server.on('message', (data) => {
    console.log('Received from server:', data);

    // Disconnect after receiving the response
    IPC.disconnect('server');
 });
});

// server.js
const IPC = require('node-ipc').default;
IPC.config.id = 'server'; // Unique ID for the server
IPC.config.retry = 1500;  // Retry interval for connections
IPC.config.silent = true; // Suppress console logs

IPC.serve(() => {
  console.log('Server is running...');
  IPC.server.on('message', (data, socket) => {
    console.log('Received from client:', data);

    // Send a reply to the client
    IPC.server.emit(socket, 'message', `Hello Client, your message "${data}" was received.`);
  });
});
// Start the IPC server
IPC.server.start();

Advantages:

  1. Easy to set up and supports multiple transport protocols.

Disadvantages:

  1. Issues with outdated interfaces and past malware concerns.

5. Custom Solutions (UDP, TCP)

Overview:Use low-level UDP or TCP for maximum efficiency.
Code Example:

// client.js
const dgram = require('dgram');

// Create a UDP client
const client = dgram.createSocket('udp4');

// Send a message to the server
const message = Buffer.from('Hello, Server!');
const SERVER_PORT = 41234;
const SERVER_HOST = '127.0.0.1';

client.send(message, SERVER_PORT, SERVER_HOST, (err) => {
  if (err) {
    console.error('Error sending message:', err);
    client.close();
  } else {
    console.log('Message sent to server.');
  }
});

// Handle server responses
client.on('message', (msg) => {
  console.log(`Received response from server: "${msg}"`);
  client.close(); // Close the client after receiving the response
});

// Handle client errors
client.on('error', (err) => {
  console.error('Client error:', err);
  client.close();
});

// server.js
const dgram = require('dgram');
// Create a UDP server
const server = dgram.createSocket('udp4');

// Handle incoming messages
server.on('message', (msg, rinfo) => {
  console.log(`Received message: "${msg}" from ${rinfo.address}:${rinfo.port}`);

  // Reply to the client
  const response = `Hello, Client! Received your message: "${msg}"`;
  server.send(response, rinfo.port, rinfo.address, (err) => {
    if (err) {
      console.error('Error sending response:', err);
    } else {
      console.log('Response sent to client.');
    }
  });
});

// Handle server errors
server.on('error', (err) => {
  console.error('Server error:', err);
  server.close();
});

// Start the server
const PORT = 41234;
server.bind(PORT, () => {
  console.log(`Server listening on port ${PORT}`);
});

Advantages:

  1. High performance and lightweight.

Disadvantages:

  1. Requires manual implementation of reliability (e.g., retries, acknowledgments).
  2. UDP is unreliable for certain scenarios

Conclusion

  1. Redis Pub/Sub: Best for simple pub/sub communication and horizontal scaling across machines.
  2. ZeroMQ: Ideal for high-performance, scalable solutions needing complex messaging patterns.
  3. Node-IPC: Suitable for smaller setups or mixed environments (local and remote IPC).
  4. Built-in Modules: Use for simpler, local-only communication setups.
  5. Custom UDP/TCP: Best for high-speed, lightweight communication, but requires more effort to implement.

For most distributed applications, Redis Pub/Sub or ZeroMQ offers the best balance of simplicity and performance

Support On Demand!

Node

Related Q&A