Using Event Emitters and Async Event Handling in NestJS

Event-driven architecture is a powerful design pattern that promotes loose coupling, scalability, and better organization of logic. In NestJS, you can leverage the built-in event emitter system to handle events asynchronously within your application.

In this module, we’ll explore how to use @nestjs/event-emitter to emit and handle events, process them asynchronously, and integrate this mechanism cleanly into your NestJS apps.


Table of Contents

  1. What Are Event Emitters in NestJS?
  2. When to Use Event Emitters
  3. Installing @nestjs/event-emitter
  4. Emitting Events
  5. Listening to Events with Listeners
  6. Async Event Handling
  7. Real-World Use Cases
  8. Best Practices
  9. Conclusion

What Are Event Emitters in NestJS?

NestJS offers first-class support for event-driven patterns using the @nestjs/event-emitter package. This allows different parts of your application to communicate indirectly through events, making your architecture more modular and decoupled.

Think of it as the pub-sub pattern:

  • Emitters publish events.
  • Listeners subscribe to and respond to events.

When to Use Event Emitters

Use event emitters when:

  • You want to decouple concerns (e.g., send a welcome email after registration).
  • You want to trigger background tasks asynchronously.
  • You need to notify other parts of your app without circular dependencies.

Installing @nestjs/event-emitter

Install the official event emitter module:

bashCopyEditnpm install @nestjs/event-emitter

Then import the module in your root or feature module:

tsCopyEdit// app.module.ts
import { Module } from '@nestjs/common';
import { EventEmitterModule } from '@nestjs/event-emitter';
import { UserModule } from './user/user.module';

@Module({
  imports: [
    EventEmitterModule.forRoot(), // globally sets up emitter
    UserModule,
  ],
})
export class AppModule {}

Emitting Events

You can inject EventEmitter2 and emit custom events from any service.

tsCopyEdit// user.service.ts
import { Injectable } from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter';

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

  async registerUser(data: any) {
    // logic to create user
    const user = { id: 1, email: data.email };

    // Emit event after user is registered
    this.eventEmitter.emit('user.registered', user);
  }
}

You can emit any object, payload, or metadata you want.


Listening to Events with Listeners

Now you need a listener to react to the emitted event.

tsCopyEdit// user.listener.ts
import { OnEvent } from '@nestjs/event-emitter';
import { Injectable } from '@nestjs/common';

@Injectable()
export class UserListener {
  @OnEvent('user.registered')
  handleUserRegisteredEvent(payload: any) {
    console.log(`User registered:`, payload);
    // e.g., send welcome email or log activity
  }
}

Make sure to register the listener in your module:

tsCopyEdit// user.module.ts
import { Module } from '@nestjs/common';
import { UserService } from './user.service';
import { UserListener } from './user.listener';

@Module({
  providers: [UserService, UserListener],
})
export class UserModule {}

Async Event Handling

Event handlers can be asynchronous by returning a Promise.

tsCopyEdit@OnEvent('user.registered')
async handleUserRegisteredEvent(payload: any) {
  await this.mailService.sendWelcomeEmail(payload.email);
}

You can also define event priorities and set wildcards for broader patterns.

Example with wildcard:

tsCopyEdit@OnEvent('user.*')
handleAllUserEvents(payload: any) {
  console.log('User event occurred:', payload);
}

Real-World Use Cases

  • User registration → Send welcome emails.
  • Order placement → Send confirmation and trigger invoice generation.
  • Password reset → Log the attempt and notify via email.
  • Notifications → Trigger real-time socket events based on app activity.

Best Practices

  • Use strongly typed event payloads for better maintainability.
  • Don’t perform heavy tasks directly in listeners—offload to background jobs if needed.
  • Avoid overusing emitters for operations that should be handled via service method calls (keep them purposeful).
  • Structure listeners in separate files/modules for clarity.

Conclusion

Event Emitters in NestJS provide a powerful abstraction for decoupling your application logic and implementing reactive flows. By emitting and listening to events, you make your application more modular, maintainable, and ready for scalability.