js

Build Complete Event-Driven Microservices Architecture with NestJS, RabbitMQ, and Redis

Learn to build scalable event-driven microservices with NestJS, RabbitMQ, and Redis. Master saga patterns, service discovery, and deployment strategies for production-ready systems.

Build Complete Event-Driven Microservices Architecture with NestJS, RabbitMQ, and Redis

As I worked on scaling a traditional monolithic application recently, I faced constant bottlenecks in synchronous communication between components. Every new feature caused cascading failures across the system. That frustration led me to explore event-driven microservices as a solution. Today, I’ll walk you through building a robust architecture using NestJS, RabbitMQ, and Redis that handles real e-commerce demands. Ready to transform how your services communicate?

Our architecture features six core services coordinated through events. When a user places an order, the Order Service publishes an OrderCreatedEvent. RabbitMQ routes this to the Inventory Service for stock checks. Upon success, a PaymentRequestedEvent triggers the Payment Service. Finally, the Notification Service confirms the order completion. This asynchronous flow prevents system-wide failures when one service struggles.

Let’s set up our monorepo foundation. I prefer a structured approach that keeps services independent yet manageable:

nest new ecommerce-microservices --package-manager npm
cd ecommerce-microservices
nest generate app api-gateway
nest generate app order-service
# Repeat for user, product, payment, notification services
nest generate library common

Install essential packages:

npm install @nestjs/microservices amqplib redis 
npm install @nestjs/config @nestjs/redis ioredis

Why do you think we separate services while sharing common libraries? It balances autonomy with consistency.

For cross-service communication, we establish a shared event contract. In our common library:

// base-event.interface.ts
export interface DomainEvent<T = any> {
  id: string;
  timestamp: Date;
  eventType: string;
  aggregateId: string;
  payload: T;
}

We then implement a base service class that handles event emission:

// base.service.ts
@Injectable()
export abstract class BaseService {
  protected events: DomainEvent[] = [];

  protected addEvent(event: DomainEvent): void {
    this.events.push(event);
  }

  protected async commitEvents(): Promise<void> {
    const events = [...this.events];
    this.events = [];
    for (const event of events) {
      await this.eventEmitter.emitAsync(event.eventType, event);
    }
  }
}

RabbitMQ becomes our nervous system. Notice how we configure durable queues to survive restarts:

// rabbitmq.config.ts
export const getRabbitMQConfig = (queue: string): RmqOptions => ({
  transport: Transport.RMQ,
  options: {
    urls: [process.env.RABBITMQ_URL],
    queue,
    queueOptions: {
      durable: true,
      arguments: { 'x-message-ttl': 60000 }
    },
    prefetchCount: 10,
    noAck: false
  }
});

In the Order Service, creating an order triggers our event chain:

// order.entity.ts
export class Order {
  constructor(public id: string, public userId: string, public items: OrderItem[]) {}

  create() {
    this.addEvent({
      eventType: 'OrderCreated',
      aggregateId: this.id,
      payload: this.serialize()
    });
  }
}

What happens if payment fails after inventory reservation? We implement sagas for transactional integrity. The Order Service orchestrates compensation actions:

// order.saga.ts
async handlePaymentFailedEvent(event: PaymentFailedEvent) {
  await this.inventoryService.reverseInventory(event.orderId);
  await this.notifyUser(`Payment failed for order ${event.orderId}`);
}

Redis solves two critical challenges: distributed caching and session management. In the Product Service:

// product.service.ts
async getProduct(id: string) {
  const cached = await this.redis.get(`product:${id}`);
  if (cached) return JSON.parse(cached);
  
  const product = await this.repository.findOne(id);
  await this.redis.set(`product:${id}`, JSON.stringify(product), 'EX', 60);
  return product;
}

For monitoring, we implement health checks that report to our API gateway:

// health.controller.ts
@Get('health')
@HealthCheck()
check() {
  return this.health.check([
    () => this.rabbitmq.pingCheck('rabbitmq'),
    () => this.redis.pingCheck('redis')
  ]);
}

Testing requires simulating our event bus. I use Jest mocks for unit tests:

// order.service.spec.ts
it('should publish OrderCreatedEvent', async () => {
  const emitSpy = jest.spyOn(eventEmitter, 'emitAsync');
  await orderService.createOrder(testOrder);
  expect(emitSpy).toHaveBeenCalledWith('OrderCreated', expect.any(Object));
});

Deployment becomes straightforward with Docker Compose:

# docker-compose.yml
services:
  rabbitmq:
    image: rabbitmq:3-management
  redis:
    image: redis:alpine
  order-service:
    build: ./apps/order-service
    depends_on:
      - rabbitmq
      - redis

Common pitfalls? I’ve learned to always set message TTLs in RabbitMQ to prevent dead-letter buildup. Also, validate Redis memory policies to avoid out-of-memory crashes during traffic spikes. Instrument everything with OpenTelemetry—you’ll thank me during 3 AM outages.

Building this transformed how I approach distributed systems. The decoupling allows teams to deploy independently while maintaining system integrity. Have you encountered scenarios where event-driven approaches solved your architectural headaches? Share your experiences below! If this guide helped you, please like and share—it helps others discover practical microservices patterns. What topics should I cover next?

Keywords: event-driven microservices, NestJS microservices architecture, RabbitMQ message queue, Redis distributed caching, microservices with Docker, saga pattern implementation, NestJS RabbitMQ integration, microservices health monitoring, distributed transaction patterns, scalable microservices deployment



Similar Posts
Blog Image
Build Production-Ready GraphQL APIs with TypeScript, Apollo Server 4, and Prisma Complete Guide

Learn to build scalable GraphQL APIs with TypeScript, Apollo Server 4, and Prisma. Complete guide covering setup, authentication, caching, testing, and production deployment.

Blog Image
Complete Guide to Integrating Next.js with Prisma ORM for Type-Safe Full-Stack Development

Learn how to integrate Next.js with Prisma ORM for type-safe database operations. Build powerful full-stack apps with seamless TypeScript integration.

Blog Image
Type-Safe GraphQL APIs with NestJS, Prisma, and Apollo: Complete Enterprise Development Guide

Learn to build production-ready type-safe GraphQL APIs with NestJS, Prisma & Apollo. Complete guide covering auth, testing & enterprise patterns.

Blog Image
Complete Guide to Next.js with Prisma ORM: Build Type-Safe Full-Stack Applications in 2024

Learn how to integrate Next.js with Prisma ORM for type-safe full-stack development. Build scalable web apps with robust database management and SSR.

Blog Image
Complete Guide to Integrating Next.js with Prisma ORM for Type-Safe Database Applications

Learn how to seamlessly integrate Next.js with Prisma ORM for type-safe, full-stack web applications. Build powerful database-driven apps with enhanced developer experience.

Blog Image
Build Scalable WebSocket Apps with Socket.io, Redis Adapter and TypeScript for Production

Build scalable real-time WebSocket apps with Socket.io, Redis adapter & TypeScript. Learn authentication, scaling, performance optimization & deployment. Start building now!