Show HN: Demitter – Distributed Node.js Event Emitter (Pub/Sub)

3 months ago 4

Extend your events across processes, threads, and machines. Demitter brings the familiar Node.js event API (via emittery) API to distributed systems, enabling real-time event communication between multiple Node.js processes with near-zero configuration.

Watch the live auction demo

Live auction system with multiple bidders across separate terminal processes - all synchronized in real-time

  • 🚀 Distributed Events: Emit events across processes, threads, or machines
  • Zero Configuration: Works out of the box with sensible defaults
  • 🎯 Familiar API: Extends the battle-tested sindresorhus/emittery library
  • 🛡️ Type Safe: Full TypeScript support
  • 📦 Lightweight: Efficient binary serialization with minimal overhead
  • 🔧 Flexible: Embedded or standalone message proxy/forwarder deployment options
npm install demitter # or pnpm install demitter # or yarn add demitter

Get distributed events running in 3 steps:

1. Start the Message Forwarder

import { createForwarder } from "demitter"; const forwarder = await createForwarder(); console.log("Forwarder ready - events can now flow between processes!");

2. Create Distributed Emitters

import { createDistributedEmitter } from "demitter"; // In Process A const emitterA = await createDistributedEmitter(); // In Process B const emitterB = await createDistributedEmitter();

3. Emit Events Across Processes

// Process A: Listen for events emitterA.on("user:login", (data) => { console.log("User logged in:", data); }); // Process B: Emit events that Process A will receive emitterB.emit("user:login", { userId: 123, timestamp: Date.now() });

That's it! Events emitted in one process are automatically received by all other connected processes.

Demitter extends the powerful emittery API with distributed capabilities. All emittery methods work exactly the same:

// All the familiar emittery methods work across processes: await emitter.emit(eventName, data); emitter.on(eventName, listener); emitter.off(eventName, listener); await emitter.once(eventName); emitter.onAny(listener); emitter.clearListeners(); // ... and many more

📖 Full API Documentation: See the complete emittery API docs for all available methods, TypeScript usage, and advanced features.

Distributed-Specific APIs

createDistributedEmitter(options?)

Creates a new distributed emitter instance.

const emitter = await createDistributedEmitter({ xsubAddress: "tcp://localhost:5555", // Connect to forwarder xpubAddress: "tcp://localhost:5556", // Connect to forwarder });

createForwarder(options?)

Creates and starts a message forwarder to enable event distribution.

const forwarder = await createForwarder({ xsubPort: 5555, // Default port for subscribers xpubPort: 5556, // Default port for publishers });

Closes the distributed emitter and cleans up connections.

  • distributed:error: Emitted when distributed operations fail (network issues, serialization errors, etc.)
emitter.on("distributed:error", (error) => { console.error("Distributed operation failed:", error); });
  • Microservices Communication: Event-driven architecture between services
  • Multi-Process Applications: Coordinate events across worker processes
  • Real-Time Systems: Live updates, notifications, and state synchronization
  • Distributed Logging: Centralized event collection from multiple sources
  • Game Development: Real-time multiplayer game state management
  • IoT Networks: Device communication and sensor data distribution

Explore practical examples in the examples/ directory:

  • basic-usage.ts - Simple distributed events setup
  • error-handling.ts - Robust error handling patterns
  • live_auction/ - Complete live auction system demo
    • Multi-process auction with bidders, controller, and spectator dashboard
    • Real-time terminal UIs with charts and analytics
    • Intelligent bidding strategies and conflict resolution
# Basic usage pnpm run example:basic # Error handling pnpm run example:errors # Live auction demo (see examples/live_auction/auction-demo/README.md) cd examples/live_auction/auction-demo pnpm install pnpm run demo

Start the forwarder within your application:

import { createForwarder, createDistributedEmitter } from "demitter"; const forwarder = await createForwarder(); const emitter = await createDistributedEmitter();

Run the forwarder as a separate service:

# Using the CLI npx demitter-forwarder # Or start with custom ports XSUB_PORT=7777 XPUB_PORT=8888 npx demitter-forwarder
FROM node:22-alpine RUN npm install -g demitter EXPOSE 5555 5556 CMD ["demitter-forwarder"]

The system consists of two main components:

  1. Pub/Sub Forwarder - A standalone message broker that forwards events between distributed processes
  2. DistributedEmitter - An extension of Emittery class in the emittery library for distributed event emission

The forwarder acts as a central message broker using ZeroMQ's XSUB-XPUB proxy pattern.

After installing the package globally:

npm install -g demitter # or pnpm install -g demitter

You can run the forwarder from anywhere:

# Start with default ports (XSUB: 5555, XPUB: 5556) demitter-forwarder # With custom ports XSUB_PORT=6000 XPUB_PORT=6001 demitter-forwarder # With debug logging LOG_LEVEL=debug demitter-forwarder # Show help demitter-forwarder --help

For projects with demitter as a dependency:

# Start with default ports (XSUB: 5555, XPUB: 5556) node src/forwarder.js # Or using the CLI script node src/cli.js # With custom ports XSUB_PORT=6000 XPUB_PORT=6001 node src/forwarder.js # With debug logging LOG_LEVEL=debug node src/forwarder.js
import { createForwarder } from "./src/forwarder.js"; // Start forwarder with default configuration const forwarder = await createForwarder(); // Custom configuration const forwarder = await createForwarder({ xsubPort: 6000, xpubPort: 6001, loggerOptions: { level: "debug" }, }); // Graceful shutdown await forwarder.close();

The forwarder can be configured using environment variables:

Variable Default Description
XSUB_PORT 5555 Port for XSUB socket (receives from publishers)
XPUB_PORT 5556 Port for XPUB socket (sends to subscribers)
XSUB_ADDRESS tcp://*:${XSUB_PORT} Full address for XSUB socket
XPUB_ADDRESS tcp://*:${XPUB_PORT} Full address for XPUB socket
LOG_LEVEL info Logging level (debug, info, warn, error)
demitter-forwarder --help # or when running locally node src/cli.js --help

See LICENSE file.

I'm open to contributions but the details for contributions are not yet finalized. If you have ideas or improvements, feel free to open an issue or start a discussion before you raise a pull request.

Read Entire Article