Skip to content
Learni
View all tutorials
Architecture Logicielle

How to Implement an Event-Driven Architecture in 2026

Lire en français

Introduction

Event-driven architecture allows application components to communicate through events instead of direct calls. This makes the system more flexible, scalable, and easier to maintain. In this tutorial you will build a simple order processing system where each step (creation, payment, shipping) reacts to events. This approach eliminates tight coupling between modules and simplifies adding new features.

Prerequisites

  • Node.js 20 or higher
  • Basic JavaScript knowledge
  • A code editor (VS Code recommended)

Project Initialization

terminal
mkdir event-driven-demo
cd event-driven-demo
npm init -y
npm install events

This command creates the project folder and initializes a Node.js project. The events module is native but we import it explicitly for clarity.

Creating the Event Bus

eventBus.js
const EventEmitter = require('events');

class EventBus extends EventEmitter {
  emitEvent(eventName, payload) {
    console.log(`[EventBus] Événement émis: ${eventName}`);
    this.emit(eventName, payload);
  }
}

const eventBus = new EventBus();
module.exports = eventBus;

We create an EventBus class that extends EventEmitter. The emitEvent method centralizes logging and event emission to simplify debugging.

Order Service

orderService.js
const eventBus = require('./eventBus');

function createOrder(orderData) {
  console.log('Commande créée:', orderData.id);
  eventBus.emitEvent('order.created', orderData);
}

module.exports = { createOrder };

The order service simply emits an 'order.created' event after creating an order. It has no knowledge of payment or shipping.

Payment Handler

paymentHandler.js
const eventBus = require('./eventBus');

eventBus.on('order.created', (order) => {
  console.log(`Paiement traité pour la commande ${order.id}`);
  eventBus.emitEvent('payment.completed', { orderId: order.id, status: 'paid' });
});

This handler subscribes to 'order.created' and emits a new 'payment.completed' event. It demonstrates event chaining without direct coupling.

Shipping Handler

shippingHandler.js
const eventBus = require('./eventBus');

eventBus.on('payment.completed', (payment) => {
  console.log(`Expédition lancée pour la commande ${payment.orderId}`);
});

The final handler reacts only to the payment event. Adding new handlers requires no changes to existing services.

Main Application

index.js
const { createOrder } = require('./orderService');
require('./paymentHandler');
require('./shippingHandler');

const newOrder = { id: 'ORD-123', amount: 49.99 };
createOrder(newOrder);

The main file imports the handlers (to register them) then triggers the flow. Run with node index.js to see the complete event flow.

Best Practices

  • Name events descriptively (order.created, payment.completed)
  • Centralize the event bus in a single module
  • Avoid side effects in handlers
  • Add clear logs on every event emission
  • Test each handler in isolation

Common Mistakes to Avoid

  • Forgetting to import handlers before emitting events
  • Using overly generic event names
  • Not handling errors in listeners
  • Creating circular dependencies between handlers

Going Further

To explore advanced event-driven architectures and patterns, check out our Learni courses.