February 10, 2025


When you're building a platform where multiple services need to talk to each other — say, a freight management system where a load-matching service, a notification service, and an invoicing service all need to react to the same event — synchronous REST calls between services create tight coupling and cascading failures.
If the invoicing service is slow, the load-matching service has to wait. If the notification service is down, the whole request fails.
RabbitMQ solves this by acting as a message broker. Services publish events to a queue and move on. Downstream services consume those messages independently, at their own pace.
Install the required packages:
npm install @nestjs/microservices amqplib amqp-connection-managerRegister the microservice transport in your main.ts:
const app = await NestFactory.createMicroservice<MicroserviceOptions>(
AppModule,
{
transport: Transport.RMQ,
options: {
urls: [process.env.RABBITMQ_URL],
queue: 'load_events',
queueOptions: { durable: true },
},
},
);NestJS gives you two decorators depending on whether you need a reply:
// Request/Reply — caller waits for a response
@MessagePattern('get_load_details')
async getLoad(data: { loadId: string }) {
return this.loadsService.findById(data.loadId);
}
// Fire-and-forget — publish and move on
@EventPattern('load_assigned')
async handleLoadAssigned(data: LoadAssignedEvent) {
await this.notificationService.notifyCarrier(data);
await this.invoiceService.createDraft(data);
}Use @MessagePattern when the publisher needs data back. Use @EventPattern for side effects that don't block the flow.
By default, RabbitMQ will push all queued messages to the first available consumer. If you have 3 worker instances and one is slow, it'll pile up all messages on that one worker.
Fix it with prefetchCount:
queueOptions: {
durable: true,
},
prefetchCount: 1, // each worker takes one message at a timeThis ensures round-robin distribution across your scaled instances.
Don't let failed messages disappear silently. Set up a dead letter exchange (DLX) to capture them:
queueOptions: {
durable: true,
arguments: {
'x-dead-letter-exchange': 'load_events.dlx',
'x-message-ttl': 60000, // retry after 60s
},
}Then consume from the DLX queue separately for alerting or manual retry.
@EventPattern for async side effects — don't block your main flow waiting on notifications or invoice draftsprefetchCount: 1 on every consumer to prevent message pile-up on a single slow worker