May 24, 2026| 11 MIN| 4 Views|Engineering

Decoupling workloads: Asynchronous Microservices with NestJS & RabbitMQ.

Learn how to architect high-throughput microservices using NestJS, event message patterns, and durable RabbitMQ queues.

MT

Muhammad Taha Siddiqui

Author

Decoupling workloads: Asynchronous Microservices with NestJS & RabbitMQ

NestJS combined with RabbitMQ is the golden standard for building scalable, decoupled, event-driven microservices. When modular codebases outgrow standard REST endpoints, an asynchronous message broker becomes essential.

๐ŸŽฏ The Architecture

Traditional monolithic backends handle database reads, authentication, image processing, and payment webhooks on a single server. A sudden spike in payment webhooks can crash the entire system. By decoupling workloads into independent microservices communicating through RabbitMQ, we ensure high system resilience and parallelized message queue processing.

๐Ÿ› ๏ธ Step-by-Step Implementation

1. Initializing the Microservice

In NestJS, setting up a microservice listener requires changing our main.ts file bootstrap process:

import { NestFactory } from '@nestjs/core';
import { MicroserviceOptions, Transport } from '@nestjs/microservices';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.createMicroservice<MicroserviceOptions>(AppModule, {
    transport: Transport.RMQ,
    options: {
      urls: [process.env.RABBITMQ_URL || 'amqp://localhost:5672'],
      queue: 'billing_queue',
      queueOptions: {
        durable: true,
      },
    },
  });
  await app.listen();
}
bootstrap();

2. Message Handling in Controllers

Using @EventPattern and @MessagePattern, we can consume events and messages asynchronously:

import { Controller } from '@nestjs/common';
import { EventPattern, Payload, Ctx, RmqContext } from '@nestjs/microservices';

@Controller()
export class BillingController {
  @EventPattern('payment_created')
  async handlePaymentCreated(@Payload() data: any, @Ctx() context: RmqContext) {
    const channel = context.getChannelRef();
    const originalMsg = context.getMessage();
    
    try {
      console.log('Processing payment invoice for: ', data.orderId);
      // Process business logic here...
      
      channel.ack(originalMsg); // Manual acknowledgment
    } catch (err) {
      // Handle retry queues / DLX (Dead Letter Exchange)
    }
  }
}

๐Ÿš€ Key Takeaways

  • โ–ธDurable Queues: Keep durable: true so that even if RabbitMQ crashes, messages are saved on disk and not lost.
  • โ–ธManual Acknowledgment: Disable auto-ACK and trigger acknowledgments manually. This ensures that if a microservice crashes mid-process, RabbitMQ puts the message back in queue to avoid data loss.

Thought it was useful?

Share this record with your engineering circle.

Keep Reading