How to Manage Events in NestJS

Updated: January 1, 2024 By: Guest Contributor Post a comment

Introduction

Managing events is crucial for building reactive NestJS applications. This guide walks you through various ways to handle events, enabling loose coupling and enhanced modularity.

Event-Driven Architecture in NestJS

Event-driven architecture within NestJS revolves around the concepts of events and handlers. Events are significant occurrences that your application needs to respond to, and handlers are the logical pieces of code that react to these events.

Let’s install the necessary dependencies for working with events in NestJS:

npm install --save @nestjs/cqrs @nestjs/event-emitter

Creating Basic Events and Handlers

To begin, we’ll create a simple user-creation event and handler.

Define an event:

import { IEvent } from '@nestjs/cqrs';

export class UserCreatedEvent implements IEvent {
  constructor(public readonly userId: string) {}
}

Implement the handler:

import { EventsHandler, IEventHandler } from '@nestjs/cqrs';
import { UserCreatedEvent } from './user-created.event';

@EventsHandler(UserCreatedEvent)
export class UserCreatedEventHandler implements IEventHandler {
  handle(event: UserCreatedEvent) {
    console.log('User created with ID:', event.userId);
  }
}

Setting Up the Event Module

For our events and handlers to work, we need to set up an event module. Here’s how you can do it:

import { Module } from '@nestjs/common';
import { CqrsModule } from '@nestjs/cqrs';
import { UserCreatedEvent } from './events/user-created.event';
import { UserCreatedEventHandler } from './handlers/user-created.handler';

@Module({
  imports: [CqrsModule],
  providers: [UserCreatedEventHandler]
})
export class EventModule {}

Pub/Sub with NestJS Event Emitter

NestJS provides an EventEmitterModule for publishing and subscribing to events:

import { Module } from '@nestjs/common';
import { EventEmitterModule } from '@nestjs/event-emitter';

@Module({
  imports: [EventEmitterModule.forRoot()],
  ...
})
export class AppModule {}

Define the event:

export class UserCreatedEvent {
  constructor(public readonly userId: string) {}
}

Publish an event:

import { EventEmitter2 } from 'eventemitter2';

constructor(private eventEmitter: EventEmitter2) {}

createUser(userId: string): void {
  this.eventEmitter.emit('user.created', new UserCreatedEvent(userId));
}

Subscribe to the event:

import { OnEvent } from '@nestjs/event-emitter';

@OnEvent('user.created')
handleUserCreatedEvent(event: UserCreatedEvent) {
  console.log('User created with ID:', event.userId);
}

Advanced Usage

Asynchronous Event Handling

In more complex systems, you might need to handle events asynchronously. Let’s delve into implementing a robust event handling system:

Use a queue-based solution for deferred processing:

import { IEvent, ofType, SagaIterator } from '@nestjs/cqrs';
import { delay, map } from 'rxjs/operators';
import { Observable } from 'rxjs';

@saga(processUserCreatedEvent$) {
  return events$
    .pipe(
      ofType(UserCreatedEvent),
      delay(1000),
      map(event => new DeferredEvent(event.userId))
    );
}

Integrating Microservices with Events

In a microservices-based application, NestJS can help ensure reliable communication between services through events:

Example using a message broker:

import { ClientKafka } from '@nestjs/microservices';

constructor(private client: ClientKafka) {}

async onModuleInit() {
  this.client.subscribeToResponseOf('user.created');
  await this.client.connect();
}

publishUserCreatedEvent(userId: string) {
  this.client.emit('user.created', new UserCreatedEvent(userId));
}

Testing Event Handlers

To ensure the reliability of your event-driven system, testing your handlers is essential:

import { Test } from '@nestjs/testing';
import { EventEmitterModule } from '@nestjs/event-emitter';
import { UserCreatedEvent } from './events/user-created.event';
import { UserCreatedEventHandler } from './handlers/user-created.handler';

describe('UserCreatedEventHandler', () => {
  let handler: UserCreatedEventHandler;

  beforeEach(async () => {
    const moduleRef = await Test.createTestingModule({
      imports: [EventEmitterModule.forRoot()],
      providers: [UserCreatedEventHandler],
    }).compile();

    handler = moduleRef.get(UserCreatedEventHandler);
  });

  it('should handle the event', () => {
    const userId = '1';
    const event = new UserCreatedEvent(userId);

    // Assume we mock our side effects here

    handler.handle(event);

    // Assert the expected side effects
  });
});

Conclusion

This guide has walked you through setting up basic to advanced event handling in your NestJS application. Events promote flexible and loosely-coupled system design, allowing your services to react and communicate effectively.