Skip to content

Node.js Events

Beyond the event loop itself, Node.js has an explicit "events" pattern used throughout its core APIs, streams, servers, and processes all emit named events you can listen for. This lesson introduces that pattern before the EventEmitter lesson goes deeper.

What Are Node.js Events?

Many Node.js objects, HTTP servers, streams, child processes, are "event emitters": objects that can announce ("emit") named events at various points in their lifecycle, and let any number of listeners react to those events without tight coupling between the emitter and its listeners.

This pattern shows up constantly: an HTTP server emits 'request' and 'close', a readable stream emits 'data' and 'end', and process itself emits 'exit' and 'SIGINT'. Understanding events is essential to reading real Node.js code.

import http from 'node:http';

const server = http.createServer((req, res) => res.end('ok'));

server.on('listening', () => {
  console.log('Server is now listening for connections');
});

server.on('close', () => {
  console.log('Server has stopped');
});

server.listen(3000);

server is an EventEmitter under the hood, .on(eventName, listener) registers a function to run whenever that named event fires.

The Emitter Pattern

emitter.on('eventName', (payload) => { ... });   // register a listener
emitter.emit('eventName', payload);               // fire the event
emitter.once('eventName', listener);              // listen only once
emitter.off('eventName', listener);                // remove a listener
  • .on() registers a listener that runs every time the event fires.
  • .once() registers a listener that automatically removes itself after firing one time.
  • .emit() synchronously calls every listener registered for that event, in registration order.
  • Multiple listeners can subscribe to the same event independently.

Node.js Events Cheatsheet

Where you'll encounter the events pattern throughout Node's core APIs.

Object Common Events
http.Server 'request', 'listening', 'close', 'error'
Readable stream 'data', 'end', 'error', 'close'
Writable stream 'drain', 'finish', 'error'
process 'exit', 'SIGINT', 'uncaughtException'
Child process 'exit', 'error', 'message'
Custom classes Extend EventEmitter to emit your own events

Why Events Instead of a Single Callback?

A single callback only supports one "this is done" notification. Real objects like servers and streams have many distinct moments worth reacting to (started listening, received data, hit an error, closed), and any number of independent listeners might care about each one. The event pattern supports all of that naturally, where a single callback parameter could not.

The 'error' Event Is Special

If an EventEmitter emits an 'error' event and there is no listener registered for it, Node.js throws the error and crashes the process. This is intentional, errors must not be silently ignored, but it means you should always attach an 'error' listener to anything that might emit one.

server.on('error', (err) => {
  console.error('Server error:', err.message);
});

Skipping this listener is one of the most common causes of a Node.js process crashing unexpectedly in production.

Common Mistakes

  • Not attaching an 'error' listener to an emitter that can emit one, causing an unhandled crash.
  • Assuming .emit() is asynchronous, listeners actually run synchronously, in registration order, during the .emit() call.
  • Registering the same listener function multiple times by accident, causing it to run more than once per event.
  • Confusing events (a Node.js core pattern) with browser DOM events, they share terminology but different APIs.

Key Takeaways

  • Many Node.js core objects are event emitters that announce named events during their lifecycle.
  • .on()/.once() register listeners; .emit() synchronously invokes them.
  • Always attach an 'error' listener to emitters that might emit one, missing it crashes the process.
  • The events pattern is foundational to streams, servers, and process lifecycle handling in Node.js.

Pro Tip

When exploring an unfamiliar Node.js library, search its source or docs for .emit( calls, it's often the fastest way to discover every meaningful lifecycle moment the library exposes, beyond what's documented in prose.