NodeJSThe events Module

The events Module

node:events provides the EventEmitter class — the beating heart of Node's architecture. HTTP servers, streams, sockets, the process object, and countless libraries are all emitters. Master this one class and a huge swath of Node's API suddenly follows the same predictable shape: .on() to listen, .emit() to fire.

The publish/subscribe model

An emitter lets one piece of code announce that something happened without knowing who (if anyone) is listening. Listeners subscribe by name; emitting calls them all, synchronously, in registration order:

JS
import { EventEmitter } from 'node:events'

const bus = new EventEmitter()

bus.on('order', (id, total) => {
  console.log(`Order ${id}: $${total}`)
})

bus.emit('order', 42, 19.99)   // → Order 42: $19.99
Order 42: $19.99
The core methods

Method

Does

on(name, fn) / addListener

Subscribe; fires every time

once(name, fn)

Subscribe; fires once, then auto-removes

emit(name, ...args)

Fire the event, passing args to listeners

off(name, fn) / removeListener

Unsubscribe a specific listener

removeAllListeners([name])

Drop all listeners (for one event or all)

listenerCount(name)

How many listeners are attached

eventNames()

Array of events with active listeners

Emitting is synchronous
`emit()` runs listeners synchronously, in order
A common misconception is that events are async. They are not: `emit` calls each listener *immediately and in turn*, finishing them all before `emit` returns. A slow listener blocks the others and the caller. If you need to yield, defer work inside the listener with `setImmediate`/`queueMicrotask` (see [setImmediate & process.nextTick](/nodejs/set-immediate)).

JS
bus.on('x', () => console.log('listener'))
console.log('before')
bus.emit('x')
console.log('after')
// Output order: before → listener → after   (NOT before → after → listener)
The special 'error' event
An unhandled `error` event crashes the process
`error` is special: if an emitter fires `'error'` and has **no** listener for it, Node throws — terminating the process with an uncaught exception. This is deliberate, so errors aren't silently swallowed. Every long-lived emitter (servers, streams) must have an `.on('error', ...)` handler. Full treatment in [Handling error Events](/nodejs/error-events).

JS
const e = new EventEmitter()

// Without this listener, the next line would CRASH the process:
e.on('error', (err) => console.error('Handled:', err.message))

e.emit('error', new Error('boom'))   // → Handled: boom
The max-listeners warning

To catch leaks, an emitter warns when more than 10 listeners are added for one event — usually a sign you're subscribing in a loop without cleaning up:

(node:123) MaxListenersExceededWarning: Possible EventEmitter memory leak
detected. 11 order listeners added to [EventEmitter]. Use
emitter.setMaxListeners() to increase limit
The warning usually means a real leak
Don't reflexively raise the limit with `setMaxListeners(50)`. The far more common cause is forgetting to `off()` listeners — e.g. adding one per request but never removing it. Each lingering listener also pins everything its closure captures, so the leak grows unbounded. Fix the cleanup first; raise the limit only when you genuinely need many listeners.
Extending EventEmitter

The idiomatic way to give your own class events is to extend EventEmitter — exactly how Node's own http.Server works:

JS
import { EventEmitter } from 'node:events'

class Job extends EventEmitter {
  async run() {
    this.emit('start')
    try {
      const result = await this.doWork()
      this.emit('done', result)
    } catch (err) {
      this.emit('error', err)
    }
  }
}

const job = new Job()
job.on('start', () => console.log('running…'))
job.on('done', (r) => console.log('finished', r))
job.on('error', (e) => console.error('failed', e))
Modern helpers

JS
import { once, on } from 'node:events'

// Await a single event as a promise:
const [result] = await once(job, 'done')

// Async-iterate a stream of events:
for await (const [data] of on(emitter, 'data')) {
  console.log(data)
}
`events.once` bridges emitters and async/await
`once(emitter, name)` returns a promise that resolves with the event's arguments — letting you `await` an event instead of nesting a callback. It also rejects if the emitter fires `'error'` first, which is exactly the behavior you want. This is the cleanest way to wait for a one-shot event.
Section complete
That completes Core Modules. Next we go deep on the pattern itself: [Event-Driven Programming](/nodejs/event-driven).