NodeJS

How to Use Event Emitters for Decoupled Code in Node.js


While building applications in Node.js the ability to scale often requires our attention to maintaining a clean and decoupled code structure. Of the several methods that can be used in Node.js in the accomplishment of this, one of the most useful non-primitive objects is the EventEmitter object in the events system. The approach of event-based programming means that while one component sends out a message, another component is able to respond to it but the two components don’t have a close interconnection, which makes testing, revision, and expansion simpler.

What is an EventEmitter?

In Node.js, the EventEmitter is a built-in class ready to underpin an event-driven approach. It lets you define your own ‘events’ which can be used to trigger other parts of your application – an ‘emitting’ part of your application can fire an event, and then other parts can listen for it and respond to it as necessary.

This pattern is particularly useful in scenarios where you want to decouple different components of your application, such as:

  • Logging system events

  • Managing asynchronous tasks

  • Triggering notifications

  • Handling user-defined workflows

Importing and Creating an EventEmitter

The events module is built into Node.js, so you don’t need to install any external packages. Here’s how to get started:

const EventEmitter = require('events');  // Create a custom EventEmitter instance  const myEmitter = new EventEmitter();

 

Emitting Events

Using the .emit() method you can send an event. Let’s say you want to log a message whenever a new user registers:

myEmitter.on('userRegistered', (user) => {    console.log(`Welcome, ${user.name}!`);  });  // Emit the event with user data  myEmitter.emit('userRegistered', { name: 'John Doe' });   

Here, the userRegistered event is triggered by the .emit() method, and the listener reacts to it by logging the welcome message.

Decoupling Code with Event Emitters

Problem with Tightly Coupled Code

In the first case tightly coupled application components communicate directly by calling each other’s methods. As this works in small projects, it becomes a problem as the application expands. When one component is altered, others might have to be changed also, and this leads to more bugs, as well as poor code maintainability.

Decoupled Approach

That is why, with the help of the EventEmitter, components do not have to know about the inner functioning of each other. A component can produce an event and it has no track on other components with capacity to receive those events.

Example: Decoupled Logging

Here’s a scenario where a file upload service emits events, and a logging service listens for them:

// fileUploadService.js  const EventEmitter = require('events');  const uploadEmitter = new EventEmitter();  function uploadFile(file) {    console.log(`Uploading ${file.name}...`);    // Simulate upload completion    setTimeout(() => {      uploadEmitter.emit('fileUploaded', file);    }, 1000);  }  module.exports = { uploadEmitter, uploadFile };  // loggingService.js  const { uploadEmitter } = require('./fileUploadService');  uploadEmitter.on('fileUploaded', (file) => {    console.log(`File uploaded: ${file.name}`);  });  // app.js  const { uploadFile } = require('./fileUploadService');  uploadFile({ name: 'example.txt' });  

 

In this setup:

  1. The uploadFile function handles the file upload and emits a fileUploaded event.

  2. The logging service listens for the fileUploaded event and logs the message.

  3. The two components are completely decoupled—neither has direct dependencies on the other.

Advanced Features

Event Listeners Count

You can check how many listeners are attached to a specific event:

console.log(myEmitter.listenerCount('userRegistered'));  

 

Removing Listeners

To avoid memory leaks or unnecessary listeners, you can remove event listeners using .removeListener() or .off():

function greet(user) {    console.log(`Hello, ${user.name}!`);  }  myEmitter.on('greet', greet);  myEmitter.off('greet', greet);

Real-World Use Cases

  1. Microservices Communication: Event emitters can facilitate communication between microservices by publishing and subscribing to events.
  2. Real-Time Notifications: Emit events for live updates in applications like chat systems or stock trackers.
  3. Middleware and Plugins: Many frameworks like Express leverage event-driven patterns for hooks and middlewares.

Best Practices

  • Avoid Memory Leaks: Ensure unused listeners are removed to prevent memory leaks. Use the maxListeners property to set a listener limit.
  • Document Events: Maintain clear documentation for all emitted events to help developers understand their purpose and usage.
  • Error Handling: Handle errors gracefully by listening for the error event or implementing a fallback mechanism.});
myEmitter.on('error', (err) => {    console.error(`An error occurred: ${err.message}`);    });

Conclusion

EventEmitter class in Node.js is one of the lightweight methods of organizing service-oriented, event-based architectures. Thus, following this kind of pattern, you may enhance code maintainability as well as modularity and scalability. Regardless of the size of your app – it is a small application that you develop over a few days for internal use in the company, or a large-scale system that will have thousands of users – the event emitter can serve as a valuable tool here.

With this fundamental knowledge in your head about HOW to use event emitters, it is time to put it to practice in your Node.js applications and receive the advantages of better and more interchangeable code.

 Ready to transform your business with our technology solution? Contact Us today to Leverage Our NodeJS Expertise.

0

NodeJS

Related Center Of Excellence