Building a Publisher-Subscriber Event Bus in JavaScript

Basic Event Bus

A minimal implementation maps topic names to arrays of handler functions.

class EventBus {
  constructor() {
    this.topics = {};
  }

  emit(topic, data) {
    const queue = this.topics[topic];
    if (!queue) {
      console.warn('No listeners for: ' + topic);
      return;
    }
    queue.forEach(handler => handler(data));
  }

  on(topic, handler) {
    if (!this.topics[topic]) {
      this.topics[topic] = [];
    }
    this.topics[topic].push(handler);
  }
}

Usage:

const bus = new EventBus();
bus.emit('alert', 'early message'); // warns: no listeners

bus.on('alert', msg => console.log('Received:', msg));
bus.emit('alert', 'broadcast complete');

Source-Verified Variant

To ensure subscribers only receive events from approved origins, the bus can validate the event source during disptach.

class VerifiedEventBus {
  constructor() {
    this.registry = {};
  }

  dispatch({ topic, payload, source }) {
    const channel = this.registry[topic];
    if (!channel) {
      console.warn('Topic unregistered: ' + topic);
      return;
    }

    if (!source) {
      throw new Error('Missing source for topic: ' + topic);
    }

    if (source === 'any') {
      channel.listeners.forEach(l => l.handler(payload));
      return;
    }

    let matched = false;
    channel.listeners.forEach(l => {
      if (l.allowedSource === source) {
        matched = true;
        l.handler(payload);
      }
    });

    if (!matched) {
      console.warn('No listener accepts source "' + source + '" on topic "' + topic + '"');
    }
  }

  register({ topic, source }, handler) {
    if (!this.registry[topic]) {
      this.registry[topic] = { listeners: [] };
    }
    this.registry[topic].listeners.push({
      allowedSource: source,
      handler
    });
  }
}

Usage:

const bus = new VerifiedEventBus();

bus.dispatch({ topic: 'news', payload: 'headline 1', source: 'agency' });
// warns: Topic unregistered: news

bus.register({ topic: 'news', source: 'agency' }, data => {
  console.log('Agency feed:', data);
});

bus.dispatch({ topic: 'news', payload: 'headline 2', source: 'agency' });
// Agency feed: headline 2

bus.dispatch({ topic: 'news', payload: 'gossip', source: 'blog' });
// warns: No listener accepts source "blog" on topic "news"

Tags: javascript pub-sub event-bus design-patterns

Posted on Thu, 07 May 2026 06:30:10 +0000 by MetroidMaster1914