Event-driven architecture in TypeScript: writing flexible code with events

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Event-driven architecture in TypeScript: when code "hears" each other

\n\n

Imagine you are writing a complex web application: a messenger, a trading bot, or a server monitoring system. At some point, you realize: simply calling functions in a chain is a nightmare. One module pulls another, a third depends on a fourth, and when the logic changes, everything falls apart like a house of cards.

\n\n

Sound familiar? Then you need event-driven architecture in TypeScript. This is not just a trendy term, but a real way to make code flexible, extensible, and understandable. In this article, we will break down how event-driven programming works, write our own event bus in TypeScript, and see where it really helps.

\n\n

What is Event-driven architecture in simple terms

\n\n

Event-driven architecture is an approach where system components communicate through events. One module generates an event (for example, "user registered"), and others subscribe to it and react. They are not directly connected to each other.

\n\n

Sounds abstract? Let's use a real-life example. Imagine an office:

\n
    \n
  • Traditional approach: The boss personally approaches each employee and says: "Make a report."
  • \n
  • Event-driven: The boss shouts: "A report is needed!" — and everyone who knows how to make it starts working on it.
  • \n
\n\n

In the second case, the boss doesn't need to know who exactly will make the report. He simply sends an event. And employees can connect and disconnect without changing the boss's logic. This is the essence of event-driven programming.

\n\n

Why TypeScript is ideal for event-driven architecture

\n\n

JavaScript itself is event-driven (remember addEventListener), but in large projects without types, chaos begins. TypeScript events solve this problem: you know exactly what data comes in the event, and the compiler will catch the error at development time, not at runtime.

\n\n

Additionally, TypeScript supports generics, overloads, and strict typing of callbacks — everything needed to create a reliable event bus.

\n\n

Writing an Event Bus in TypeScript from scratch

\n\n

Let's start with a simple but working example. Let's create a class EventBus — a central event bus through which all modules will communicate.

\n\n
// event-bus.ts\ntype EventHandler<T = any> = (data: T) => void;\n\nclass EventBus {\n  private handlers: Map<string, EventHandler[]> = new Map();\n\n  // Subscribe to an event\n  on<T>(event: string, handler: EventHandler<T>): void {\n    if (!this.handlers.has(event)) {\n      this.handlers.set(event, []);\n    }\n    this.handlers.get(event)!.push(handler as EventHandler);\n  }\n\n  // Unsubscribe from an event\n  off(event: string, handler: EventHandler): void {\n    const handlers = this.handlers.get(event);\n    if (handlers) {\n      this.handlers.set(event, handlers.filter(h => h !== handler));\n    }\n  }\n\n  // Emit an event\n  emit<T>(event: string, data: T): void {\n    const handlers = this.handlers.get(event);\n    if (handlers) {\n      handlers.forEach(handler => handler(data));\n    }\n  }\n}\n\nexport const globalEventBus = new EventBus();
\n\n

And there we have it — we've written a simple event-driven architecture in TypeScript. Now any module can import globalEventBus and either listen to events or generate them.

\n\n

Usage example: notifications and logging

\n\n

Let's imagine we have an authentication module and a notification module. They should not know about each other directly.

\n\n
// auth-module.ts\nimport { globalEventBus } from './event-bus';\n\nexport function loginUser(email: string): void {\n  // ... login logic\n  console.log(`User                

Blogs

Book Recommendations