TypeScript

How to develop an event driven app in NodeJS

Instead of calling functions directly, you fire an event. Something else picks it up and reacts. That's event-driven programming in a sentence.

17 Apr 2024

How to develop an event driven app in NodeJS

Instead of calling functions directly, you fire an event. Something else picks it up and reacts. That's event-driven programming in a sentence.

Node.js was built around this idea. The event loop, streams, HTTP servers — they all use events under the hood. The EventEmitter class is the primitive that makes it work.

Basic usage

Typescript
const EventEmitter = require("events");

const emitter = new EventEmitter();

emitter.on("order:created", (order) => {
  console.log(`Processing order ${order.id}`);
});

emitter.emit("order:created", { id: 1, total: 99.99 });

on registers a listener. emit fires the event. That's the entire API at its core.

A real-world pattern

Say you have an order system. When an order is placed, you need to:

  1. Save it to the database
  2. Send a confirmation email
  3. Update inventory

Without events, you'd stuff all three into one function. With events, each concern subscribes independently:

Typescript
emitter.on("order:placed", saveToDatabase);
emitter.on("order:placed", sendConfirmationEmail);
emitter.on("order:placed", updateInventory);

emitter.emit("order:placed", orderData);

Each handler is independent. You can add, remove, or change one without touching the others.

The trade-off

Event-driven code decouples your components. That's the benefit.

The cost is traceability. When something goes wrong, you can't just follow the call stack. You have to trace which listeners are registered, in what order, and whether any of them threw. Debugging event-driven systems is harder than debugging direct function calls.

Also, EventEmitter is in-process only. If you need events across services, you need a message broker like Redis Pub/Sub, RabbitMQ, or Kafka. The EventEmitter pattern stays the same, but the infrastructure underneath changes completely.

Use events when you want loose coupling and multiple reactions to a single trigger. Use direct calls when the flow is linear and debuggability matters more than flexibility.