Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
40 prioritized NestJS best practices across architecture, DI, security, performance, testing, and microservices.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
rules/micro-use-patterns.md
1---2title: Use Message and Event Patterns Correctly3impact: MEDIUM4impactDescription: Proper patterns ensure reliable microservice communication5tags: microservices, message-pattern, event-pattern, communication6---78## Use Message and Event Patterns Correctly910NestJS microservices support two communication patterns: request-response (MessagePattern) and event-based (EventPattern). Use MessagePattern when you need a response, and EventPattern for fire-and-forget notifications. Understanding the difference prevents communication bugs.1112**Incorrect (using wrong pattern for use case):**1314```typescript15// Use @MessagePattern for fire-and-forget16@Controller()17export class NotificationsController {18@MessagePattern('user.created')19async handleUserCreated(data: UserCreatedEvent) {20// This WAITS for response, blocking the sender21await this.emailService.sendWelcome(data.email);22// If email fails, sender gets an error (coupling!)23}24}2526// Use @EventPattern expecting a response27@Controller()28export class OrdersController {29@EventPattern('inventory.check')30async checkInventory(data: CheckInventoryDto) {31const available = await this.inventory.check(data);32return available; // This return value is IGNORED with @EventPattern!33}34}3536// Tight coupling in client37@Injectable()38export class UsersService {39async createUser(dto: CreateUserDto): Promise<User> {40const user = await this.repo.save(dto);4142// Blocks until notification service responds43await this.client.send('user.created', user).toPromise();44// If notification service is down, user creation fails!4546return user;47}48}49```5051**Correct (use MessagePattern for request-response, EventPattern for fire-and-forget):**5253```typescript54// MessagePattern: Request-Response (when you NEED a response)55@Controller()56export class InventoryController {57@MessagePattern({ cmd: 'check_inventory' })58async checkInventory(data: CheckInventoryDto): Promise<InventoryResult> {59const result = await this.inventoryService.check(data.productId, data.quantity);60return result; // Response sent back to caller61}62}6364// Client expects response65@Injectable()66export class OrdersService {67async createOrder(dto: CreateOrderDto): Promise<Order> {68// Check inventory - we NEED this response to proceed69const inventory = await firstValueFrom(70this.inventoryClient.send<InventoryResult>(71{ cmd: 'check_inventory' },72{ productId: dto.productId, quantity: dto.quantity },73),74);7576if (!inventory.available) {77throw new BadRequestException('Insufficient inventory');78}7980return this.repo.save(dto);81}82}8384// EventPattern: Fire-and-Forget (for notifications, side effects)85@Controller()86export class NotificationsController {87@EventPattern('user.created')88async handleUserCreated(data: UserCreatedEvent): Promise<void> {89// No return value needed - just process the event90await this.emailService.sendWelcome(data.email);91await this.analyticsService.track('user_signup', data);92// If this fails, it doesn't affect the sender93}94}9596// Client emits event without waiting97@Injectable()98export class UsersService {99async createUser(dto: CreateUserDto): Promise<User> {100const user = await this.repo.save(dto);101102// Fire and forget - doesn't block, doesn't wait103this.eventClient.emit('user.created', {104userId: user.id,105email: user.email,106timestamp: new Date(),107});108109return user; // User creation succeeds regardless of event handling110}111}112113// Hybrid pattern for critical events114@Injectable()115export class OrdersService {116async createOrder(dto: CreateOrderDto): Promise<Order> {117const order = await this.repo.save(dto);118119// Critical: inventory reservation (use MessagePattern)120const reserved = await firstValueFrom(121this.inventoryClient.send({ cmd: 'reserve_inventory' }, {122orderId: order.id,123items: dto.items,124}),125);126127if (!reserved.success) {128await this.repo.delete(order.id);129throw new BadRequestException('Could not reserve inventory');130}131132// Non-critical: notifications (use EventPattern)133this.eventClient.emit('order.created', {134orderId: order.id,135userId: dto.userId,136total: dto.total,137});138139return order;140}141}142143// Error handling patterns144// MessagePattern errors propagate to caller145@MessagePattern({ cmd: 'get_user' })146async getUser(userId: string): Promise<User> {147const user = await this.repo.findOne({ where: { id: userId } });148if (!user) {149throw new RpcException('User not found'); // Received by caller150}151return user;152}153154// EventPattern errors should be handled locally155@EventPattern('order.created')156async handleOrderCreated(data: OrderCreatedEvent): Promise<void> {157try {158await this.processOrder(data);159} catch (error) {160// Log and potentially retry - don't throw161this.logger.error('Failed to process order event', error);162await this.deadLetterQueue.add(data);163}164}165```166167Reference: [NestJS Microservices](https://docs.nestjs.com/microservices/basics)168