DEV Community

Cover image for Event-Driven Architecture with NestJS: Using the EventEmitter Module
Ezile Mdodana
Ezile Mdodana

Posted on

Event-Driven Architecture with NestJS: Using the EventEmitter Module

In today's software development landscape, building scalable and maintainable applications is a primary concern for developers. Event-driven architecture (EDA) is a powerful design pattern that helps address these concerns by promoting decoupled and asynchronous communication between different parts of a system. NestJS, a progressive Node.js framework, provides robust support for EDA through its EventEmitter module. This article explores the benefits of EDA and demonstrates how to implement it using the EventEmitter module in a NestJS application.

Understanding Event-Driven Architecture

Event-driven architecture is a design paradigm in which system components communicate by producing and consuming events. An event is a significant change in state, such as a user action or system occurrence. This architecture consists of three main components:

  1. Event Producers: Generate events when certain actions or changes occur.
  2. Event Consumers: Listen for and react to specific events.
  3. Event Channels: Transport events from producers to consumers.

EDA offers several benefits:

  • Decoupling: Components are loosely coupled, reducing dependencies and making the system more modular and maintainable.

  • Scalability: Asynchronous event handling allows the system to scale more effectively.

  • Responsiveness: Real-time event processing leads to more responsive applications.

NestJS and the EventEmitter Module

NestJS is a framework that leverages TypeScript to build efficient and scalable server-side applications. It incorporates many design patterns and best practices from Angular, providing a structured way to develop applications. One of the key features of NestJS is its support for event-driven programming through the EventEmitter module.

The EventEmitter module in NestJS is built on top of the Node.js events module, offering a simple yet powerful way to implement EDA. It allows you to define events, emit them from various parts of your application, and handle them with dedicated listeners.

Setting Up a NestJS Application

Before diving into the implementation, let's set up a basic NestJS application. If you haven't already, install the Nest CLI globally:

npm install -g @nestjs/cli
Enter fullscreen mode Exit fullscreen mode

Create a new NestJS project:

nest new event-driven-app
cd event-driven-app
Enter fullscreen mode Exit fullscreen mode

Install the EventEmitter module:

npm install @nestjs/event-emitter
Enter fullscreen mode Exit fullscreen mode

Implementing Event-Driven Architecture

Defining Events

First, define the events your application will use. Create a events directory in the src folder, and within it, create an events.ts file:

// src/events/events.ts

export class UserCreatedEvent {
  constructor(public readonly userId: string, public readonly username: string) {}
}
Enter fullscreen mode Exit fullscreen mode

Emitting Events

Next, emit events from appropriate parts of your application. For demonstration purposes, let's assume we have a UserService that creates users and emits a UserCreatedEvent when a new user is created.

Create a user directory in the src folder, and within it, create user.service.ts:

// src/user/user.service.ts

import { Injectable } from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { UserCreatedEvent } from '../events/events';

@Injectable()
export class UserService {
  constructor(private eventEmitter: EventEmitter2) {}

  createUser(userId: string, username: string): void {
    // Logic to create a user (e.g., saving to a database)

    // Emit UserCreatedEvent
    const event = new UserCreatedEvent(userId, username);
    this.eventEmitter.emit('user.created', event);
  }
}
Enter fullscreen mode Exit fullscreen mode

Handling Events

Now, create an event listener to handle the UserCreatedEvent. Create a listeners directory in the src folder, and within it, create user-created.listener.ts:

// src/listeners/user-created.listener.ts

import { Injectable } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter';
import { UserCreatedEvent } from '../events/events';

@Injectable()
export class UserCreatedListener {
  @OnEvent('user.created')
  handleUserCreatedEvent(event: UserCreatedEvent) {
    // Logic to handle the event (e.g., sending a welcome email)
    console.log(`User created: ${event.userId} (${event.username})`);
  }
}
Enter fullscreen mode Exit fullscreen mode

Registering the Listener

Finally, register the event listener in your application's module. Open app.module.ts and update it as follows:

// src/app.module.ts

import { Module } from '@nestjs/common';
import { EventEmitterModule } from '@nestjs/event-emitter';
import { UserService } from './user/user.service';
import { UserCreatedListener } from './listeners/user-created.listener';

@Module({
  imports: [
    EventEmitterModule.forRoot(),
  ],
  providers: [UserService, UserCreatedListener],
})
export class AppModule {}
Enter fullscreen mode Exit fullscreen mode

Running the Application

With everything set up, you can now run your NestJS application:

npm run start:dev
Enter fullscreen mode Exit fullscreen mode

Test the event-driven functionality by creating a user through the UserService:

// src/main.ts

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { UserService } from './user/user.service';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  const userService = app.get(UserService);

  userService.createUser('1', 'Test User');

  await app.listen(3000);
}
bootstrap();
Enter fullscreen mode Exit fullscreen mode
Upon running the application, you should see the following output, indicating that the UserCreatedEvent was emitted and handled:

User created: 1 (Test User)
Enter fullscreen mode Exit fullscreen mode

Conclusion

Event-driven architecture is a powerful approach for building scalable, maintainable, and responsive applications. NestJS's EventEmitter module makes it easy to implement EDA by providing a simple yet effective way to define, emit, and handle events. By following the steps outlined in this article, you can start leveraging the benefits of EDA in your NestJS applications, leading to more modular and efficient codebases.

My way is not the only way!

Top comments (0)