Lawrence Eagles Senior full-stack developer, writer, and instructor.

How to build custom Node.js event emitters

8 min read 2305

Node Logo Over Wire Connectors

Editor’s note: This post was updated 13 September 2022 to include information about why to use event emitters, the class members within EventEmitter, and make other general updates to the text.

Events are actions that have software or hardware significance. They are emitted due to either user activities, such as a mouse click or keystrokes, or directly from systems, such as errors or notifications.

The JavaScript language enables us to respond to events by running code within an event handler. Since Node.js is based on JavaScript, it leverages this feature in its event-driven architecture.

In this article, we’ll discuss what event emitters are and why we should use them, as well as how to build custom event emitters. Let’s get started!

System events vs. custom events

Events in Node.js are actions that occur in our application that we can respond to. There are two different types of events in Node.js, both of which we’ll cover in this article:

1. System events

System events occur from the C++ side of the Node.js core and are handled by a C library used in Node.js called libuv.

The libuv library deals with lower-level events that are happening inside of the computer system, essentially, low-level events that come from the OS, such as receiving data from the internet, finished reading a file, and so on.

Because the flow of a Node.js program is determined by events (event-driven programming), all I/O requests would eventually generate a completion/failure event.

Side note: JavaScript does not have built-in capabilities to handle system events.

2. Custom events

Custom events occur inside the JavaScript core in Node.js. These events are handled by a Node.js class called EventEmitter.

EventEmitter is a JavaScript class in Node.js implemented to handle custom events because JavaScript does not have any built-in capability to handle events otherwise.

Often, when an event occurs in libuv, libuv generates custom JavaScript events so they can be easily handled. Consequently, Node.js events might be seen as a single entity even though they are actually two different types of events.

In this article, our focus will be on custom JavaScript events and the event emitter.

What are event emitters?

Node.js uses the event-driven programming pattern as noted above. This design pattern is for producing and consuming events and is predominantly used in the frontend (browser). User actions such as a button click or a keypress generate an event.

These events have associated functions called listeners, which are called to handle their corresponding events when they are emitted.

The event-driven pattern has two main components:

  1. An event handler (listener). This is the function that would be called when the event is emitted. More than one listener can be listening for an event. When the event is emitted, these listeners would be called synchronously.
  2. A main loop that listens for events and calls the associated listeners to handle that event.

Node.js brings event-driven programming to the backend through the EventEmitter class. The EventEmitter class is a part of the events core Node.js module. It provides a way for us to emit and handle events, and it exposes — among many others — the on and the emit methods.

In this article, we will build a custom event emitter by using the core principles that powers Node.js event emitters so that we can better understand how they work.

The code below shows how to emit and handle an event using the EventEmitter object.

More great articles from LogRocket:

const EventEmitter = require('events');
const eventEmitter = new EventEmitter ();

eventEmitter.on('greet', () => {
  console.log('Hello world!');


In the code above, the eventEmitter.on method is used to register listeners that handle an event, while the eventEmitter.emit method is used to emit events.

By emitting the greet event above, the function that listens for the greet event is called, and Hello world! is logged to the console.

Other methods exposed by the EventEmitter objects are:

  1. eventEmitter.once(): This adds a one-time listener
  2. This is an alias for eventEmitter.removeListener(). It removes an event listener from an event
  3. eventEmitter.listnersCount(): This returns the number of listeners listening to an event

In Node.js, there are other objects that can emit events. However, all objects that emit events are an instance of the EventEmitter class. To best understand the event emitters, let’s build our own.

Why use event emitters?

Node.js brings event-driven programming to the backend through the EventEmitter class. The EventEmitter class is a part of the events core Node.js module.

Event emitters and listeners are crucial to Node.js development. They enable the Node.js runtime to accomplish its single-threaded, asynchronous, nonblocking I/O feature that is the crux of Node.js’ awesome performance.

Class members within EventEmitter

The Node.js EventEmitter class provides a way for us to emit and handle events. Within it, it exposes many class members:

  • The emit method sequentially and synchronously calls each of the listeners listening for the fired event — passing the supplied argument to each
  • The on method takes two arguments: the event name and an event listener. It adds this event listener function to the end of the array of listeners
  • off is an alias for the emitter.removeListener()
  • The removeListener method takes two arguments: the event name and an event listener. Then it removes this event listener from the array of events when the specified event fires
  • addListener is an alias for the on method

The general pattern for a custom event emitter

The general idea of an event emitter is to have an event object whose keys act as custom events, and the corresponding values would be arrays of listeners that are invoked synchronously when the event is emitted.

Consider the code below:

const GreetHandlers = [
  ()=> {console.log("Hello world!"), 
  ()=> {console.log("Hello Developer!"), 
  ()=> {console.log("Hello From LogRocket!"}

const events = {
  "greet": GreetHandlers

Here, the events object has one property: "greet". Every property name of this object is considered a unique event, meaning that the "greet" property is an event.

The value of the "greet" property (event) is the GreetHandlers array. This is an array containing functions (or listeners) that would be called synchronously when the "greet" event occurs.

To call these functions synchronously, we loop through the array and invoke each function, as seen below:

GreetHandlers.forEach(listener => {

// Output:
// "Hello world!"
// "Hello Developer!"
// "Hello From LogRocket!"

Our example above gives a simplified overview of the pattern that is used in the Node.js event emitter. We will employ the same pattern as we build our own event emitter in the next section.

Building a custom event emitter in Node.js

Although the Node.js EventEmitter is a JavaScript class, we will be building our own using a function constructor so that we can understand what is happening in the background.

Classes in JavaScript give us a new and easy syntax to work with the JavaScript’s prototypal inheritance. Because classes in JavaScript are syntactic sugar for the prototypal pattern, many things occur under the hood that is hidden from us.

To build our custom event emitter, follow the steps below:

1. Create the event emitter function constructor

This should have one property (the event object).

function Emitter( ) { = { };

The events object above would serve as the main object that holds all custom events. Each key and value of this object corresponds to an event and its array of listeners.

function Emitter( ) { = {
      "greet": [
                  ()=> {},
                  ()=> {},
                  ()=> {}, 
      "speak": [
                  ()=> {},
                  ()=> {},
                  ()=> {}, 

2. Add methods to the event emitter prototype

Object-oriented JavaScript gives us a clean way to share properties and methods across our app. This is because the prototypal pattern allows objects to access properties down the prototype chain, meaning an object can access properties in its prototype, in the prototype of its prototype, and beyond.

The JavaScript engine first searches for a method or property in the prototype of an object if it is absent in the object. If it does not find it in the prototype of that object, it continues its search down the prototype chain. This pattern of inheritance is known as prototypal inheritance.

Because of JavaScript’s prototypal inheritance, when we add properties and method to an object’s prototype, all instances of that object would have access to them.

See here in the code below, where we add the on method:

Emitter.prototype.on = function (type, listener) {
    // check if the listener is a function and throw error if it is not
    if(typeof listener !== "function") {
        throw new Error("Listener must be a function!")
    // create the event listener property (array) if it does not exist.[type] =[type] || []; 
    // adds listners to the events array.[type].push(listener); 

This adds the on function to the prototype of the Emitter object, which allows all instances of the Emitter object to inherit this method. The on method takes two arguments, namely type and listener (a function).

First, the on method checks if the listener is a function. If it is not, it throws an error as seen below:

if(typeof listener !== "function") {
    throw new Error("Listener must be a function!")

Also, the on method checks if the type of event is present in the events object (as a key). If it isn’t present, it adds an event (as a key) to the events object and sets its value to an empty array. Finally, it adds listeners to the corresponding event array:[type].push(listener);.

Now let’s add the emit method:

Emitter.prototype.emit = function(type) {
    if ([type]) { // checks if event is a property on Emitter[type].forEach(function(listener) { 
        // loop through that events array and invoke all the listeners inside it.
            listener( );

// if used as a node module. Exports this function constructor
modules.exports = Emitter; 
// This makes it available from the require() 
// so we can make as many instances of it as we want.

The code above adds the emit method to the Emitters prototype. It simply checks if the event type is present (as a key) in the events object. If it is present, it then invokes all the corresponding listeners as already discussed above.[type] returns the value of the corresponding event property, which is an array containing listeners.

Consequently, the code below loops through the array and invokes all its listeners synchronously.[type].forEach(function(listener) { 
    // loop through that events array and invoke all the listeners inside it.
        listener( );

To use our event emitter, we would have to manually emit an event.

// if used in a Node.js environment require the module as seen below.
const Emitter = require('./emitter'); 

const eventEmitter = new Emitter();

eventEmitter.on('greet', ()=> {
  console.log("Hello World!");

eventEmitter.on('greet', ()=> {
  console.log("Hello from LogRocket!");

eventEmitter.emit("greet"); // manually emit an event 

In the code above, we first require and create an instance of the Emitter module using this:

const Emitter = require('./emitter'); 

const eventEmitter = new Emitter(); 

Then we assign listeners to the "greet" event using the on method.

eventEmitter.on('greet', ()=> {
  console.log("Hello World!");

eventEmitter.on('greet', ()=> {
  console.log("Hello from LogRocket!");

Finally, we manually emit the greet event using this line:


Code Results

The image above shows the result of running our code. View the full code in a sandbox here.

Adding more event methods

The addListener method

It is important to note that the on method is actually an alias for the addListener method in the event emitter. Thus, we need to refactor out implementation.

Emitter.prototype.addListener = function (type, listener) {
  // check if the listener is a function and throw error if it is not
  if (typeof listener !== "function") {
    throw new Error("Listener must be a function!");
  // create the event listener property (array) if it does not exist.[type] =[type] || [];
  // adds listners to the events array.[type].push(listener);
Emitter.prototype.on = function (type, listener) {
  return this.addListener(type, listener);

The above code still works, but in this version, both the addListener and the on method do the same thing.

The listenersCount method

There’s also the listenersCount method. This returns the total number of functions (listeners) that are listening for a particular event. We will implement this below:

Emitter.prototype.listenerCount = function (type) {
  let listnersCount = 0;
  let listeners =[type] || [];
  listnersCount = listners.length;
  console.log("listeners listnersCount", listnersCount);
  return listnersCount;

Here, we tell the simple store the array of listeners for the specified event to the listeners variable using:

let listeners =[type] || []; 

If the event is not found, an empty array is stored. Then, we return the length of the listener variable.

Event emitters in Node.js follow this same idea, but they have a lot of extra features. We just built a simple version. You can play with final code in plain JavaScript here.


The event emitter is a fundamental building block of many parts of the Node.js JavaScript core. All Node.js objects that emit events, such as streams and the HTTP module, are instances of the EventEmitter class. It is an important object in Node.js that is defined and exposed by the events module.

Through the EventEmitter class, Node.js brings event-driven programming to the server side. I hope by building our contrived event emitter that you have been able to learn more about the Node.js EventEmitter class.

200’s only Monitor failed and slow network requests in production

Deploying a Node-based web app or website is the easy part. Making sure your Node instance continues to serve resources to your app is where things get tougher. If you’re interested in ensuring requests to the backend or third party services are successful, try LogRocket.

LogRocket is like a DVR for web and mobile apps, recording literally everything that happens while a user interacts with your app. Instead of guessing why problems happen, you can aggregate and report on problematic network requests to quickly understand the root cause.

LogRocket instruments your app to record baseline performance timings such as page load time, time to first byte, slow network requests, and also logs Redux, NgRx, and Vuex actions/state. .
Lawrence Eagles Senior full-stack developer, writer, and instructor.

One Reply to “How to build custom Node.js event emitters”

Leave a Reply