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.