eventemitter3 vs mitt vs pubsub-js
Event Handling Patterns in JavaScript Applications
eventemitter3mittpubsub-jsSimilar Packages:

Event Handling Patterns in JavaScript Applications

eventemitter3, mitt, and pubsub-js are libraries used to manage event communication within JavaScript applications. eventemitter3 offers a Node.js-like EventEmitter API optimized for browsers, providing a familiar interface for backend developers. mitt is a tiny 200-byte alternative focused on simplicity and modern frontend needs, stripping away legacy features. pubsub-js provides a global static API for publish-subscribe messaging, decoupling senders and receivers without requiring instance management.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
eventemitter303,52074.4 kB212 months agoMIT
mitt011,84626.4 kB253 years agoMIT
pubsub-js04,873113 kB29a year agoMIT

Event Handling Patterns in JavaScript Applications

When building interactive web applications, components need to talk to each other without creating tight dependencies. eventemitter3, mitt, and pubsub-js all solve this problem, but they take different approaches to API design and scope. Let's compare how they handle common engineering tasks.

πŸ—οΈ API Style: Instances vs Global Static

The biggest difference lies in how you create and use the event system.

eventemitter3 requires you to create an instance for each event source.

  • You import the class and initialize it.
  • This allows multiple independent event channels.
// eventemitter3: Instance-based
import EventEmitter from 'eventemitter3';

const emitter = new EventEmitter();

emitter.on('update', (data) => {
  console.log(data);
});

emitter.emit('update', { id: 1 });

mitt also uses an instance-based approach.

  • You call the default export to create a bus.
  • Similar to eventemitter3 but with a smaller footprint.
// mitt: Instance-based
import mitt from 'mitt';

const emitter = mitt();

emitter.on('update', (data) => {
  console.log(data);
});

emitter.emit('update', { id: 1 });

pubsub-js uses a global static API.

  • You do not create instances.
  • All events go through a single central hub.
// pubsub-js: Global static
import PubSub from 'pubsub-js';

PubSub.subscribe('update', (msg, data) => {
  console.log(data);
});

PubSub.publish('update', { id: 1 });

πŸ› οΈ Feature Set: Basic vs Advanced

Not all libraries support the same level of control.

eventemitter3 includes advanced tools like once and removeAllListeners.

  • once listens for a single event then removes itself.
  • removeAllListeners clears every handler for a topic.
// eventemitter3: Advanced features
emitter.once('login', () => {
  // Runs only once
});

emitter.removeAllListeners('login');

mitt keeps things minimal.

  • It only supports on, off, and emit.
  • You must manually manage one-time listeners if needed.
// mitt: Basic features only
const handler = (data) => {
  emitter.off('update', handler);
  console.log(data);
};

emitter.on('update', handler);

pubsub-js focuses on subscription management.

  • It returns a token when you subscribe.
  • You use that token to unsubscribe later.
// pubsub-js: Token-based unsubscribe
const token = PubSub.subscribe('update', (msg, data) => {
  console.log(data);
});

PubSub.unsubscribe(token);

🧩 Scope and Isolation

How you organize events affects code maintenance.

eventemitter3 encourages isolation.

  • You can pass specific emitters to components.
  • This makes testing easier because you can mock specific instances.
// eventemitter3: Passing instances
function Component({ emitter }) {
  emitter.on('click', handleClick);
}

mitt also supports isolation.

  • You can create multiple buses for different domains.
  • Useful for separating UI events from data events.
// mitt: Multiple buses
const uiBus = mitt();
const dataBus = mitt();

uiBus.on('hover', handleHover);
dataBus.on('load', handleLoad);

pubsub-js uses a global scope.

  • Any part of the app can publish or subscribe.
  • This can lead to hidden dependencies if not documented well.
// pubsub-js: Global scope
// Any file can import and publish
PubSub.publish('global-event', payload);

πŸ›‘οΈ Type Safety and Modern Development

TypeScript support varies between these tools.

eventemitter3 has strong TypeScript definitions.

  • You can define event maps for type safety.
  • Helps catch errors during development.
// eventemitter3: Typed events
interface Events {
  update: { id: number };
}

const emitter = new EventEmitter<Events>();

mitt is built with TypeScript in mind.

  • It uses generics to enforce event payloads.
  • Very concise type definitions.
// mitt: Typed events
import mitt from 'mitt';

type Events = {
  update: { id: number };
};

const emitter = mitt<Events>();

pubsub-js has weaker type support.

  • The global nature makes typing specific topics harder.
  • Often requires manual type assertions.
// pubsub-js: Less strict typing
PubSub.subscribe('update', (msg, data: any) => {
  // Data type is often loosely defined
});

🌐 Real-World Scenarios

Scenario 1: Component Communication

You need to pass events between nested components without prop drilling.

  • βœ… Best choice: mitt
  • Why? Small size and instance-based design fit well with component lifecycles.
// mitt: Component bus
const bus = mitt();
// Pass bus via context or props

Scenario 2: Complex State Management

You need to track listeners, remove them all on cleanup, or listen once.

  • βœ… Best choice: eventemitter3
  • Why? Built-in once and removeAllListeners simplify cleanup logic.
// eventemitter3: Cleanup
emitter.removeAllListeners();

Scenario 3: Decoupled Modules

You have independent modules that should not know about each other.

  • βœ… Best choice: pubsub-js
  • Why? Global hub allows modules to communicate without sharing references.
// pubsub-js: Decoupled
// Module A publishes
PubSub.publish('data-ready', data);
// Module B subscribes independently

πŸ“Œ Summary Table

Featureeventemitter3mittpubsub-js
API StyleInstanceInstanceGlobal Static
Bundle SizeSmallTinySmall
Once Supportβœ… Built-in❌ Manual⚠️ Varies
Type Safetyβœ… Strongβœ… Strong⚠️ Weak
Isolationβœ… Highβœ… High❌ Low

πŸ’‘ Final Recommendation

Think about scope and complexity.

  • Need multiple isolated channels? β†’ Use eventemitter3 (feature-rich) or mitt (minimal).
  • Need a global message bus? β†’ Use pubsub-js.
  • Need strict types and cleanup tools? β†’ Use eventemitter3.

These tools help keep your code organized β€” choose the one that matches your project's structure.

How to Choose: eventemitter3 vs mitt vs pubsub-js

  • eventemitter3:

    Choose eventemitter3 if you need a robust, Node.js-compatible event API with features like once and removeAllListeners in a browser environment. It is ideal for complex applications where you need multiple isolated emitters and strong typing support.

  • mitt:

    Choose mitt if bundle size is critical and you only need basic on/off/emit functionality without legacy baggage. It works best for simple state buses or component communication where you do not need advanced event features.

  • pubsub-js:

    Choose pubsub-js if you prefer a global static API for loose coupling and do not need to manage multiple emitter instances. It suits projects where a central message hub is preferred over passing emitter objects around.

README for eventemitter3

EventEmitter3

Version npmCICoverage Status

EventEmitter3 is a high performance EventEmitter. It has been micro-optimized for various of code paths making this, one of, if not the fastest EventEmitter available for Node.js and browsers. The module is API compatible with the EventEmitter that ships by default with Node.js but there are some slight differences:

  • Domain support has been removed.
  • We do not throw an error when you emit an error event and nobody is listening.
  • The newListener and removeListener events have been removed as they are useful only in some uncommon use-cases.
  • The setMaxListeners, getMaxListeners, prependListener and prependOnceListener methods are not available.
  • Support for custom context for events so there is no need to use fn.bind.
  • The removeListener method removes all matching listeners, not only the first.

It's a drop in replacement for existing EventEmitters, but just faster. Free performance, who wouldn't want that? The EventEmitter is written in EcmaScript 3 so it will work in the oldest browsers and node versions that you need to support.

Installation

$ npm install --save eventemitter3

CDN

Recommended CDN:

https://unpkg.com/eventemitter3@latest/dist/eventemitter3.umd.min.js

Usage

After installation the only thing you need to do is require the module:

var EventEmitter = require('eventemitter3');

And you're ready to create your own EventEmitter instances. For the API documentation, please follow the official Node.js documentation:

http://nodejs.org/api/events.html

Contextual emits

We've upgraded the API of the EventEmitter.on, EventEmitter.once and EventEmitter.removeListener to accept an extra argument which is the context or this value that should be set for the emitted events. This means you no longer have the overhead of an event that required fn.bind in order to get a custom this value.

var EE = new EventEmitter()
  , context = { foo: 'bar' };

function emitted() {
  console.log(this === context); // true
}

EE.once('event-name', emitted, context);
EE.on('another-event', emitted, context);
EE.removeListener('another-event', emitted, context);

Tests and benchmarks

To run tests run npm test. To run the benchmarks run npm run benchmark.

Tests and benchmarks are not included in the npm package. If you want to play with them you have to clone the GitHub repository. Note that you will have to run an additional npm i in the benchmarks folder before npm run benchmark.

License

MIT