EventEmitter Basics
6 examples to get you started with EventEmitter - 4 basic and 2 intermediate.
Prerequisites
import { EventEmitter } from 'node:events'.- Understand Memory Leaks from Listeners for production services.
Basic Examples
1. Create and Emit
Register listeners and emit named events synchronously.
import { EventEmitter } from 'node:events';
const bus = new EventEmitter();
bus.on('ping', (ms: number) => console.log('pong', ms));
bus.emit('ping', 42);emitcalls listeners synchronously on the same tick unlessasynclisteners return Promises you handle separately.- Event names are strings or symbols - prefer constants for domain events.
- Return value of
emitis true if listeners existed.
Related: Domain Events vs EventEmitter - when to use a bus
2. once for One-Shot Handlers
Automatically remove listener after first invocation.
import { EventEmitter } from 'node:events';
const emitter = new EventEmitter();
emitter.once('ready', () => console.log('startup complete'));
emitter.emit('ready');
emitter.emit('ready'); // second emit does nothing- Ideal for bootstrapping and waiting for
'open'events on servers. oncewrapsonwith autooff- avoids leak if event fires exactly once.- For repeated events, use
onwith explicitoffin cleanup.
3. off / removeListener
Remove listeners when scope ends.
import { EventEmitter } from 'node:events';
const bus = new EventEmitter();
function handler(payload: string) {
console.log(payload);
}
bus.on('job', handler);
bus.off('job', handler);- Pass the same function reference to
off- anonymous functions cannot be removed withoutremoveAllListeners. - Critical for long-lived processes and hot reload paths.
- Prefer
AbortSignalpattern for grouped cleanup (see memory leaks page).
4. emitter.setMaxListeners
Raise limit when many listeners are intentional.
import { EventEmitter } from 'node:events';
const emitter = new EventEmitter();
emitter.setMaxListeners(50);- Default 10 triggers
MaxListenersExceededWarning- signals possible leak. - Increasing limit without justification hides leaks. Fix: remove listeners or use one fan-out handler.
getMaxListeners()reads current setting.
Related: Memory Leaks from Listeners - leak patterns
Intermediate Examples
5. Async Listener Pitfall
async listeners return Promises emit does not await.
import { EventEmitter } from 'node:events';
const bus = new EventEmitter();
bus.on('work', async () => {
await Promise.resolve();
console.log('done');
});
bus.emit('work');
console.log('emit returned');
// emit returned, then done- Errors in async listeners become unhandled rejections unless you try/catch inside.
- Do not assume ordering across async work without explicit queue.
- For async fan-out, use a job queue (BullMQ) or
events.onasync iterator with care.
6. Typed Event Map (Preview)
Type-safe events in TypeScript - full patterns on dedicated page.
import { EventEmitter } from 'node:events';
type Events = {
userCreated: [userId: string];
error: [err: Error];
};
class TypedBus extends EventEmitter {
emit<E extends keyof Events>(event: E, ...args: Events[E]): boolean {
return super.emit(event, ...args);
}
on<E extends keyof Events>(event: E, listener: (...args: Events[E]) => void): this {
return super.on(event, listener);
}
}- Prevents typo event names at compile time.
- See Typed EventEmitter for production-ready utilities.
Stack versions: This page was written for Node.js 24.18.0 (Active LTS), npm 10+, TypeScript 5.6+, Express 5, Fastify 5, and NestJS 11.