Event Patterns
Event Patterns are the routing mechanism used by NestJS microservices for Event-Based (Fire-and-Forget) communication. They allow a microservice to listen for events without needing to send a response back to the publisher.
Overview
While @MessagePattern() is used for Request-Response (the client waits for an answer), @EventPattern() is used for Publish-Subscribe (the client announces something happened and immediately moves on).
If an OrdersService creates a new order, it shouldn’t have to wait for the EmailService to send a receipt, the InventoryService to update stock, and the AnalyticsService to log the metric. It just publishes an order_created event, and all interested services listen for it using @EventPattern().
Key Concepts
@EventPattern(): The decorator used to listen for a specific event.- Fire-and-Forget: The sender (Client) does not wait for a response from the receiver(s).
- Multiple Listeners: A single event emitted by a client can trigger multiple
@EventPattern()handlers across different microservices simultaneously (depending on the Transporter used).
Code Examples
1. Defining an Event Handler
Just like message patterns, event patterns can be strings or objects.
// email-service/app.controller.ts
import { Controller } from '@nestjs/common';
import { EventPattern, Payload } from '@nestjs/microservices';
@Controller()
export class EmailController {
// Listen for the 'order_created' event
@EventPattern('order_created')
async handleOrderCreated(@Payload() data: { email: string, orderId: string }) {
console.log(`Sending confirmation email to ${data.email}`);
// Simulate sending email...
await this.emailService.sendOrderConfirmation(data.email, data.orderId);
// Notice we do NOT return anything!
// The sender doesn't care, and won't receive it anyway.
}
}
2. Multiple Services Listening
If you are using a Pub/Sub transporter (like Redis, NATS, Kafka, or RabbitMQ Fanout Exchanges), another service can listen to the exact same event.
// analytics-service/app.controller.ts
import { Controller } from '@nestjs/common';
import { EventPattern } from '@nestjs/microservices';
@Controller()
export class AnalyticsController {
// Listens to the EXACT same event as the EmailController
@EventPattern('order_created')
handleOrderCreated(data: any) {
console.log('Logging order creation in analytics database...');
this.analyticsDb.insert(data);
}
}
3. The Client Publishing the Event
To trigger these event patterns, the client must use client.emit() instead of client.send().
// orders-service/app.controller.ts
import { Controller, Post, Inject } from '@nestjs/common';
import { ClientProxy } from '@nestjs/microservices';
@Controller('orders')
export class OrdersController {
constructor(@Inject('MESSAGING_SERVICE') private client: ClientProxy) {}
@Post()
async createOrder() {
const newOrder = { orderId: '123', email: 'user@example.com' };
// Save to local DB first...
// Announce to the world!
// emit() returns an Observable, but it resolves immediately once the message
// is successfully sent to the broker. It does NOT wait for the microservices to finish.
this.client.emit('order_created', newOrder);
return 'Order successfully placed!';
}
}
Best Practices
- Do Not Return Values: While NestJS won’t necessarily crash if you return a value from an
@EventPattern()method, the value is completely ignored and thrown away. Don’t waste CPU cycles calculating return values for events. - Transporter Behavior Matters: The behavior of
@EventPatternchanges drastically based on your Transporter.- If you use TCP, and try to
emitan event to a service with 3 instances, NestJS will just pick one instance to send the event to. - If you use Redis, and have 3 different microservices subscribed, all 3 will receive it.
- If you use Kafka and the services share a
groupId, only one will receive it; if they have differentgroupIds, all will receive it. Understand your broker!
- If you use TCP, and try to