Nest JS with Event

In NestJS, you should use events when you want to decouple different parts of your application and handle certain actions asynchronously.

Sopheaktra

Eang Sopheaktra

January 31 2025 10:55 pm

0 260

Why need event for notification instead of direct to logic?

  • Decoupling Logic: Separation of concerns that does not need to know how or where the notification is handled. This allows you to focus on business logic without being tightly coupled to the notification system.
  • Scalability: while your application grows, the notification requirements may become more complex. With an event-driven approach, you can add new notification methods (like SMS, email, push notifications, etc.) by simply subscribing to the event without modifying the original service.
  • Asynchronous Processing: Sending notifications (like email or SMS) can be a time-consuming operation. By using events, you can offload these tasks to background processes, improving the performance and responsiveness of your application. Instead of blocking the main thread while waiting for a notification to be sent, you emit an event and let the listener handle the task asynchronously.
    Also improving user experience by avoiding delays when interacting with the core functionality.
  • Flexibility and Extensibility: Events allow you to add new behaviors (like notifications) without changing the existing logic of your application.
  • Centralized Event Management: Instead of having different services or components calling notification functions directly, you can centralize the event handling logic. This makes the system easier to manage, especially in larger applications where multiple actions might trigger different types of notifications.
    Also have a more centralized way of controlling the flow of events, such as adding conditions or filters before certain notifications are sent.
  • Handling Complex Notification Workflows: Some workflows might require multiple steps and using events makes it easy to chain these steps, where each one can be handled by a separate listener, providing better separation of concerns and clarity in your code.

Why @nestjs/event-emitter?

  • Decoupling Components: Event emitters allow different parts of your application to communicate without being tightly coupled. This makes the system more flexible and easier to maintain.
  • Simplified Asynchronous Communication: It provides a clean API for emitting and subscribing to events, enabling easy implementation of asynchronous workflows.
  • Built-In Support for Wildcards and Namespaces: The package uses EventEmitter2 under the hood, which supports advanced features like wildcards (*) and namespaces for events.
    • Example: this.eventEmitter.emit('order.*', { orderId: 123 });
  • Integration with NestJS's Dependency Injection: integrates with the NestJS DI system, making it easy to inject the event emitter or listeners into your services and modules.

Code

Let's start to create enum, class, interface and decorator:

  • EventType: is enum for direction to which's event 
export enum EventType {
  SEND_EMAIL = 'SEND_EMAIL',
  TELEGRAM = 'TELEGRAM',
}
  • EventHandlerType: is decorator for put on Handler to handle base on EventType
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);
  };
}
  • EvenData: is class for sending to event to process 
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;
  }
}
  • EventHandler: is interface for inherit by another class to add on event handler
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.

  • SendEmailHandler: is event handler for supporting email's notification for example
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
  }
}
  • TelegramHandler: is event handler for telegram's notification.
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);
  }
}

Summary

For notifications improves code modularity, scalability, and flexibility. It allows you to:

  • Keep notification logic separate from the core application logic.
  • Handle notifications asynchronously without blocking other processes.
  • Easily extend and modify notification workflows without disrupting the main application flow.

Comments

Subscribe to our newsletter.

Get updates about new articles, contents, coding tips and tricks.

Weekly articles
Always new release articles every week or weekly released.
No spam
No marketing, share and spam you different notification.
© 2023-2025 Tra21, Inc. All rights reserved.