Tutorials Logic, IN info@tutorialslogic.com
Navigation
Home About Us Contact Us Blogs FAQs
Tutorials
All Tutorials
Services
Academic Projects Resume Writing Website Development
Practice
Quiz Challenge Interview Questions Certification Practice
Compiler Tools

Node.js Events — EventEmitter with Examples

Events in Node.js

Node.js is often described as event-driven, and the events system is one of the reasons the platform feels so natural for servers, streams, real-time applications, and asynchronous workflows. Instead of constantly checking whether something has happened, code can register interest in an event and respond when that event is emitted. This style is based on the observer pattern, where one part of the program emits notifications and another part listens for them.

The built-in events module provides the EventEmitter class, which is the foundation of many Node.js core features. Streams, HTTP servers, file operations, process events, and many third-party libraries rely on events internally. Understanding EventEmitter helps you understand not only custom events you write yourself, but also how many important Node.js APIs are designed.

Why Events Matter

  • They let one part of the application react to something that happened elsewhere.
  • They fit naturally with asynchronous and non-blocking Node.js code.
  • They help decouple code by separating "what happened" from "what should respond."
  • They are heavily used by core modules such as streams, HTTP, and process.
  • They make custom app features like logging, notifications, and workflow hooks easier to design.

The Basic Event Flow

A simple event system usually follows three steps:

  • Create an EventEmitter object.
  • Register one or more listeners for a named event.
  • Emit the event when something important happens.

When the event is emitted, Node.js immediately calls the listeners that were registered for that event. This gives your code a structured way to react to actions such as user registration, file upload completion, payment success, or data loading.

Importing EventEmitter

To use custom events in Node.js, start by importing the events module and creating an EventEmitter instance.

Create EventEmitter
const EventEmitter = require("events");

const emitter = new EventEmitter();

After creating the emitter, you can attach named listeners and emit events whenever your program reaches the appropriate point.

Registering an Event Listener

The on() method is used to bind a listener to an event name. Every time that event is emitted, the listener runs.

Bind Event Listener
emitter.on("welcome", () => {
    console.log("Welcome event received");
});

Event names are just strings. Good event names usually describe something that happened, such as userCreated, paymentSuccess, fileUploaded, or orderCancelled.

Emitting an Event

The emit() method triggers the event and runs all listeners attached to that event name.

Emit an Event
emitter.emit("welcome");

If a listener has been registered for welcome, it runs when the event is emitted. If no listener exists, nothing visible happens. This is one reason events are flexible: the emitter does not need to know which specific actions will respond.

Complete Example: User Registration Flow

A good beginner example is a registration workflow. After a user is registered, one part of the app might welcome the user, another might log the event, and another might send an email. Events help separate those reactions from the core registration logic.

Registration Event Example
const EventEmitter = require("events");
const emitter = new EventEmitter();

emitter.on("registration", username => {
    console.log(`Registration successful for ${username}`);
});

emitter.on("registration", username => {
    console.log(`Sending welcome email to ${username}`);
});

emitter.emit("registration", "Aman");

This example also shows that one event can have multiple listeners. When registration is emitted, all listeners for that event run in the order they were registered.

Passing Data Through Events

Events often need to carry information, such as a username, file name, status object, or error message. The emit() method allows you to pass arguments after the event name, and listeners receive those values as parameters.

Event Arguments
emitter.on("orderPlaced", (orderId, amount) => {
    console.log(`Order ${orderId} placed for amount ${amount}`);
});

emitter.emit("orderPlaced", "ORD-101", 499.99);

Passing data this way makes events more useful and avoids the need to rely on shared global state.

Important EventEmitter Methods

The EventEmitter API has several useful methods beyond on() and emit().

MethodDescription
on(event, listener)Registers a listener that runs every time the event is emitted.
once(event, listener)Registers a listener that runs only the first time the event occurs.
emit(event, ...args)Triggers the event and passes any arguments to listeners.
off(event, listener)Removes a specific listener.
removeAllListeners(event)Removes all listeners for the event.
listenerCount(event)Returns how many listeners are attached to an event.

Using once()

Sometimes an event should be handled only one time. For example, you may want a connection-ready listener to fire only when the first successful connection happens. In these cases, once() is a better choice than on().

Listen Once
const EventEmitter = require("events");
const emitter = new EventEmitter();

emitter.once("connected", () => {
    console.log("Connected successfully");
});

emitter.emit("connected"); // runs
emitter.emit("connected"); // ignored

This is useful when a response should happen only the first time an event is observed.

Creating a Custom EventEmitter Class

A powerful pattern in Node.js is to create your own class that extends EventEmitter. This allows the class to emit meaningful events as part of its normal behavior. It is a clean way to add event-driven capabilities to your own code.

Custom EventEmitter Class
const EventEmitter = require("events");

class DownloadManager extends EventEmitter {
    startDownload(fileName) {
        console.log(`Starting download for ${fileName}...`);

        setTimeout(() => {
            this.emit("completed", fileName);
        }, 1000);
    }
}

const manager = new DownloadManager();

manager.on("completed", fileName => {
    console.log(`Download completed for ${fileName}`);
});

manager.startDownload("report.pdf");

This pattern is helpful when building services, managers, workers, or utility classes that need to notify other parts of the application about progress, success, failure, or state changes.

Events and Asynchronous Thinking

It is important to separate two related but different ideas: events and asynchronous operations. Events are a notification mechanism. Asynchronous operations are tasks that complete later. Often, asynchronous code emits events when work progresses or finishes, but the two concepts are not identical. For example, a stream may emit a data event every time a chunk arrives, and an end event when the stream is finished. The asynchronous file or network operation is the underlying process, while the emitted events are how your code is informed about what happened.

This is why events fit so naturally into Node.js. Instead of blocking while waiting for slow work to finish, Node.js can continue doing other things and fire the relevant listeners at the right moment.

Real-World Event Examples in Node.js

Events appear in many built-in Node.js APIs:

  • Streams emit events like data, end, and error.
  • HTTP request/response objects may emit events when body data arrives.
  • Process emits events like exit and uncaughtException.
  • Readable and writable file operations often use event-driven patterns internally.

This means that learning custom EventEmitter code is not isolated knowledge. It helps you understand the shape of many other Node.js APIs as well.

Handling Errors in Event-Driven Code

In event-driven code, errors should be handled carefully. A common Node.js convention is to emit an error event when something goes wrong. If an emitter uses error events, listeners should usually be attached so failures are not ignored.

Error Event Example
const EventEmitter = require("events");
const emitter = new EventEmitter();

emitter.on("error", error => {
    console.error("Something went wrong:", error.message);
});

emitter.emit("error", new Error("Database connection failed"));

In many Node.js scenarios, failing to handle an important error event can cause the application to crash or behave unpredictably.

Removing Listeners

If listeners are no longer needed, they should be removed. This helps avoid memory leaks or repeated handlers that continue reacting after their work is done.

Remove Event Listener
function logMessage(message) {
    console.log(message);
}

emitter.on("message", logMessage);
emitter.emit("message", "First call");

emitter.off("message", logMessage);
emitter.emit("message", "Second call"); // no output

Node.js also warns when too many listeners are added to the same event, because that can be a sign of leaking listeners over time.

Common Beginner Mistakes

One common mistake is thinking events themselves are asynchronous by default. In reality, emit() calls listeners synchronously in the order they were registered. Another mistake is registering listeners repeatedly inside loops or request handlers without removing them, which can lead to duplicate output and memory leak warnings. Beginners also sometimes expect an emitted event to "wait" for async code inside a listener, but that is not how EventEmitter works. If the listener starts asynchronous work, that work continues separately after the listener returns.

Another common issue is choosing unclear event names. Names like doStuff are vague, while names like userRegistered, fileSaved, or paymentFailed clearly communicate what happened. Good event naming makes event-driven systems much easier to follow.

A Practical Mental Model

Think of events as signals. One part of your application raises the signal when something meaningful happens, and any interested listeners respond. This allows the emitter to stay focused on its own task while other code reacts in its own way. That separation is one of the biggest strengths of event-driven design in Node.js.

Key Takeaways
  • The events module provides the EventEmitter class used throughout Node.js.
  • on() registers a listener, once() registers a one-time listener, and emit() triggers the event.
  • One event can have multiple listeners, and event data can be passed as extra arguments to emit().
  • Event emitters are useful for decoupling application logic and reacting to important actions or state changes.
  • emit() calls listeners synchronously, even though events are often used in asynchronous workflows.
  • Remove listeners when they are no longer needed to avoid leaks and duplicate behavior.

Ready to Level Up Your Skills?

Explore 500+ free tutorials across 20+ languages and frameworks.