In NestJS, you should use events when you want to decouple different parts of your application and handle certain actions asynchronously.
Eang Sopheaktra
January 31 2025 10:55 pm
Let's start to create enum, class, interface and decorator:
export enum EventType {
SEND_EMAIL = 'SEND_EMAIL',
TELEGRAM = 'TELEGRAM',
}
import { SetMetadata } from '@nestjs/common';
import { EventType } from '../enums/event-type.enum';
export const EVENT_HANDLER = 'EVENT_HANDLER';
export function EventHandlerType(type: EventType) {
return (target: any) => {
SetMetadata(EVENT_HANDLER, type)(target);
};
}
import { IsEnum, IsNotEmpty, IsObject } from 'class-validator';
import { EventType } from '../enums/event-type.enum';
export class EventData {
@IsEnum(EventType, { message: 'Invalid event type' })
@IsNotEmpty({ message: 'Event type should not be empty' })
eventType: EventType;
@IsObject({ message: 'Data should be an object' })
@IsNotEmpty({ message: 'Data should not be empty' })
data?: any;
constructor(eventType: EventType, data?: any) {
this.eventType = eventType;
this.data = data;
}
}
import { EventData } from './dtos/event.dto';
export interface EventHandler {
handle(event: EventData): Promise<void>;
}
clean it up now we already finished our class, interface, enum and decotor. Next step is create handler to implement to EventHandler.
import { Injectable } from '@nestjs/common';
import { EventHandler } from '../event-handler.interface';
import { EventData } from '../dtos/event.dto';
import { EventHandlerType } from '../decorators/event-handler.decorator';
import { EventType } from '../enums/event-type.enum';
@Injectable()
@EventHandlerType(EventType.SEND_EMAIL)
export class SendEmailHandler implements EventHandler {
constructor(private readonly notificationService: NotificationService) {}
async handle(event: EventData): Promise<void> {
// Email sending logic here
}
}
import { Injectable } from '@nestjs/common';
import { EventHandler } from '../event-handler.interface';
import { EventData } from '../dtos/event.dto';
import { EventHandlerType } from '../decorators/event-handler.decorator';
import { EventType } from '../enums/event-type.enum';
@Injectable()
@EventHandlerType(EventType.TELEGRAM)
export class TelegramHandler implements EventHandler {
constructor(
private readonly telegramService: TelegramService,
private readonly settingService: SettingService,
private readonly cryptoService: CryptoService,
) {}
async handle(event: EventData): Promise<void> {
//telegram sending logic here
}
}
For 2 example of my notification otherwise you can add on if you need more. After this setup I will create EventProcessor for process to correct direction notification handler.
import { Injectable, OnModuleInit, Inject } from '@nestjs/common';
import { EventHandler } from './event-handler.interface';
import { EventData } from './dtos/event.dto';
import { Reflector } from '@nestjs/core';
import { EVENT_HANDLER } from './decorators/event-handler.decorator';
import { EventType } from './enums/event-type.enum';
@Injectable()
export class EventProcessor implements OnModuleInit {
private handlers: { [key: string]: EventHandler } = {};
constructor(
private reflector: Reflector,
@Inject('EVENT_HANDLERS') private readonly eventHandlers: EventHandler[],
) {}
onModuleInit() {
this.eventHandlers.forEach((handler) => {
const eventType = this.reflector.get<EventType>(
EVENT_HANDLER,
handler.constructor,
);
if (eventType) {
this.registerHandler(eventType, handler);
}
});
}
registerHandler(eventType: EventType, handler: EventHandler) {
this.handlers[eventType] = handler;
}
async process(event: EventData): Promise<void> {
const handler = this.handlers[event.eventType];
if (handler) {
await handler.handle(event);
} else {
console.warn(`No handler found for event type: ${event.eventType}`);
}
}
}
This processor is reflect to decorate I have been created above and direction to my event handler for notification. Also it's register all my event from my enum so everyone can add on more event handler and add your unique for your event handler key on the enum above.
Yaaayyyy everything look clean now so finally we can create controller for create some API to notification to your hanlders.
So I created EventsController for API to consume trigger to event base on user body request.
import { Controller, Post, Body } from '@nestjs/common';
import { EventData } from '../dtos/event.dto';
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
import { EventEmitter2 } from '@nestjs/event-emitter';
@ApiBearerAuth()
@ApiTags('events')
@Controller('events')
export class EventsController {
constructor(private readonly eventEmitter: EventEmitter2) {}
@Post('trigger')
async triggerEvent(@Body() event: EventData) {
this.eventEmitter.emit('event.global', event);
return { message: 'Event triggered successfully' };
}
}
Also for cleanup your controller you need to create service to handle your business logic. Do not write all your logic on controller it's bad habit and make your code messy.
EventsService: is for handle event when triggered and send to event processor
import { Injectable, Logger } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter';
import { EventProcessor } from '../event.processor';
import { EventData } from '../dtos/event.dto';
@Injectable()
export class EventsService {
private logger = new Logger(EventsService.name);
constructor(private readonly eventProcessor: EventProcessor) {}
@OnEvent('event.global')
async handleEventTriggered(event: EventData) {
this.logger.log('Event Global Triggered:', event);
await this.eventProcessor.process(event);
}
}
For notifications improves code modularity, scalability, and flexibility. It allows you to: