Browse TypeScript Design Patterns & Application Architecture

Publish/Subscribe Pattern in TypeScript

Explore the Publish/Subscribe Pattern in TypeScript, a design pattern that decouples components through a message broker, enhancing scalability and flexibility in event-driven architectures.

6.13 Publish/Subscribe Pattern

The Publish/Subscribe (Pub/Sub) pattern is a powerful design pattern that facilitates communication between components in a software system. It achieves this by decoupling the components that produce information (publishers) from those that consume it (subscribers) through an intermediary known as a broker or event bus. This pattern is particularly useful in event-driven architectures, where it promotes scalability, flexibility, and maintainability.

Understanding the Publish/Subscribe Pattern

Intent and Definition

The primary intent of the Publish/Subscribe pattern is to allow components to communicate with each other without being directly connected. This is achieved by introducing a broker or event bus that manages the distribution of messages between publishers and subscribers. Publishers emit messages without needing to know who will receive them, while subscribers listen for messages without needing to know who sent them.

Problems Solved

The Publish/Subscribe pattern addresses several key problems in software design:

  • Decoupling Components: By using a broker, the pattern reduces dependencies between components, allowing them to evolve independently.
  • Scalability: The pattern supports the addition of new publishers and subscribers without impacting existing components.
  • Flexibility: Subscribers can dynamically register or unregister for messages, making the system adaptable to changing requirements.
  • Maintainability: With decoupled components, the system becomes easier to maintain and extend.

Key Components of the Publish/Subscribe Pattern

Let’s delve into the core components of the Publish/Subscribe pattern:

Publisher

A publisher is responsible for sending messages or events to the broker. It does not need to know the details of the subscribers or how many there are. The publisher’s role is to focus on producing messages and delegating the distribution to the broker.

Subscriber

A subscriber registers interest in certain types of messages or events. It listens for messages from the broker and processes them accordingly. Subscribers can dynamically subscribe or unsubscribe from messages, allowing for flexible system behavior.

Broker/Event Bus

The broker, also known as the event bus, is the intermediary that manages the distribution of messages between publishers and subscribers. It ensures that messages are delivered to all interested subscribers. The broker abstracts the communication details, allowing publishers and subscribers to remain unaware of each other’s existence.

Visualizing the Publish/Subscribe Pattern

To better understand the relationships between these components, let’s visualize the Publish/Subscribe pattern:

    graph TD;
	    Publisher1 -->|Publish Message| Broker;
	    Publisher2 -->|Publish Message| Broker;
	    Broker -->|Distribute Message| Subscriber1;
	    Broker -->|Distribute Message| Subscriber2;
	    Broker -->|Distribute Message| Subscriber3;

Diagram Description: This diagram illustrates the flow of messages in the Publish/Subscribe pattern. Publishers send messages to the broker, which then distributes them to all registered subscribers.

Implementing the Publish/Subscribe Pattern in TypeScript

Let’s explore how to implement the Publish/Subscribe pattern in TypeScript. We’ll create a simple event bus that allows publishers to emit events and subscribers to listen for them.

Step 1: Define the Event Bus

First, we’ll define an EventBus class that will act as the broker. This class will manage the registration of subscribers and the distribution of events.

 1type Callback = (data: any) => void;
 2
 3class EventBus {
 4    private subscribers: Map<string, Callback[]> = new Map();
 5
 6    // Register a subscriber for a specific event
 7    subscribe(eventType: string, callback: Callback): void {
 8        if (!this.subscribers.has(eventType)) {
 9            this.subscribers.set(eventType, []);
10        }
11        this.subscribers.get(eventType)!.push(callback);
12    }
13
14    // Unregister a subscriber for a specific event
15    unsubscribe(eventType: string, callback: Callback): void {
16        const callbacks = this.subscribers.get(eventType);
17        if (callbacks) {
18            this.subscribers.set(eventType, callbacks.filter(cb => cb !== callback));
19        }
20    }
21
22    // Publish an event to all subscribers
23    publish(eventType: string, data: any): void {
24        const callbacks = this.subscribers.get(eventType);
25        if (callbacks) {
26            callbacks.forEach(callback => callback(data));
27        }
28    }
29}

Code Explanation:

  • The EventBus class maintains a map of event types to arrays of callbacks.
  • The subscribe method allows subscribers to register for specific events.
  • The unsubscribe method allows subscribers to unregister from events.
  • The publish method sends data to all subscribers of a given event type.

Step 2: Implement Publishers and Subscribers

Next, we’ll implement a simple publisher and subscriber to demonstrate how they interact with the EventBus.

 1// Publisher
 2class Publisher {
 3    constructor(private eventBus: EventBus) {}
 4
 5    publishEvent(eventType: string, data: any): void {
 6        console.log(`Publishing event: ${eventType}`);
 7        this.eventBus.publish(eventType, data);
 8    }
 9}
10
11// Subscriber
12class Subscriber {
13    constructor(private eventBus: EventBus, private name: string) {}
14
15    subscribeToEvent(eventType: string): void {
16        this.eventBus.subscribe(eventType, this.handleEvent.bind(this));
17    }
18
19    handleEvent(data: any): void {
20        console.log(`${this.name} received event data:`, data);
21    }
22}
23
24// Usage
25const eventBus = new EventBus();
26const publisher = new Publisher(eventBus);
27const subscriber1 = new Subscriber(eventBus, 'Subscriber 1');
28const subscriber2 = new Subscriber(eventBus, 'Subscriber 2');
29
30subscriber1.subscribeToEvent('testEvent');
31subscriber2.subscribeToEvent('testEvent');
32
33publisher.publishEvent('testEvent', { message: 'Hello, World!' });

Code Explanation:

  • The Publisher class uses the EventBus to publish events.
  • The Subscriber class registers for events and handles them when they occur.
  • In the usage example, two subscribers register for a testEvent, and the publisher emits this event with a message.

Promoting Scalability and Loose Coupling

The Publish/Subscribe pattern inherently promotes scalability and loose coupling:

  • Scalability: New publishers and subscribers can be added without affecting existing components. The broker handles the distribution of messages, allowing the system to grow organically.
  • Loose Coupling: Publishers and subscribers are decoupled, meaning they do not need to know about each other’s existence. This reduces dependencies and makes the system more flexible and maintainable.

Advanced Topics in Publish/Subscribe

Handling Complex Event Flows

In more complex systems, events may need to be processed in a specific order or require additional context. This can be achieved by enhancing the EventBus to support features like event prioritization, filtering, or transformation.

Integrating with External Systems

The Publish/Subscribe pattern can be extended to integrate with external systems, such as message queues or cloud-based event services. This allows for distributed event processing and further scalability.

Try It Yourself

To deepen your understanding of the Publish/Subscribe pattern, try modifying the code examples:

  • Add a New Event Type: Create a new event type and have the publisher emit it. Add a new subscriber to listen for this event.
  • Implement Unsubscribe Logic: Enhance the Subscriber class to allow unsubscribing from events.
  • Integrate with a Message Queue: Explore integrating the EventBus with a message queue like RabbitMQ or Kafka for distributed event processing.

Conclusion

The Publish/Subscribe pattern is a versatile and powerful design pattern that enhances the scalability, flexibility, and maintainability of software systems. By decoupling components through a broker, it allows for dynamic and adaptable architectures that can evolve with changing requirements.

Quiz Time!

Loading quiz…

In this section

Revised on Thursday, April 23, 2026