js

Build Production-Ready Event-Driven Microservices with NestJS, RabbitMQ, and Docker: Complete Guide

Learn to build production-ready event-driven microservices with NestJS, RabbitMQ & Docker. Complete guide with deployment, monitoring & error handling.

Build Production-Ready Event-Driven Microservices with NestJS, RabbitMQ, and Docker: Complete Guide

Here’s a comprehensive guide to building production-ready event-driven microservices:

I’ve been thinking about robust microservices architectures lately. When systems grow, direct service-to-service calls become tangled webs of dependencies. That’s why I want to share how we can build resilient systems using event-driven patterns. If you find this useful, please like, share, and comment with your experiences!

Event-driven architectures solve fundamental scaling problems. Services communicate through events rather than direct requests. When a user registers, we don’t call the email service directly. Instead, we publish an event. Any interested service can react. How might this change how you design systems?

Let’s start with our foundation. We’ll use NestJS for its clean architecture and TypeScript support. RabbitMQ handles messaging with persistence guarantees. Docker containers package everything. This combination gives us portability and scalability.

Our workspace structure keeps things organized:

microservices-system/
├── services/
│   ├── user-service/
│   ├── order-service/
│   └── notification-service/
├── shared/events/
└── docker-compose.yml

Shared events are critical. They’re our contracts between services. Here’s how we define a user creation event:

// shared/events/user.events.ts
export interface UserCreatedEvent {
  eventType: 'USER_CREATED';
  data: {
    userId: string;
    email: string;
    name: string;
  };
  metadata: {
    correlationId: string; // Trace events across services
    timestamp: Date;
  };
}

Notice the correlationId. This helps track requests across services. Without it, debugging distributed systems becomes painful. Have you struggled with tracing requests in microservices before?

Now, let’s build the user service. First, our user entity:

// user-service/src/entities/user.entity.ts
@Entity('users')
export class User {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column({ unique: true })
  email: string;

  @Column()
  passwordHash: string;

  @CreateDateColumn()
  createdAt: Date;
}

The event publisher handles RabbitMQ interactions:

// user-service/src/events/event-publisher.service.ts
@Injectable()
export class EventPublisherService {
  private channel: amqp.Channel;

  async publishEvent(routingKey: string, event: object) {
    this.channel.publish(
      'user_events', 
      routingKey,
      Buffer.from(JSON.stringify(event)),
      { persistent: true } // Survive broker restarts
    );
  }
}

The persistent flag ensures messages aren’t lost if RabbitMQ restarts. In production, we’d add retry logic and dead letter queues. What happens if a service crashes while processing an event?

When a user registers, we publish an event:

// user-service/src/services/user.service.ts
async registerUser(dto: RegisterDto) {
  const user = await this.userRepo.save({ ...dto });
  
  await this.publisher.publishEvent('user.created', {
    eventType: 'USER_CREATED',
    data: { userId: user.id, email: user.email },
    metadata: { correlationId: randomUUID() }
  });

  return user;
}

The notification service listens for these events:

// notification-service/src/event-listeners/user.listener.ts
@RabbitSubscribe({
  exchange: 'user_events',
  routingKey: 'user.created',
  queue: 'notifications_queue'
})
async handleUserCreated(event: UserCreatedEvent) {
  await this.emailService.sendWelcome(event.data.email);
}

We configure queues to be durable so they survive broker restarts. For error handling, we implement dead letter exchanges:

// Notification service setup
await channel.assertExchange('dlx', 'direct');
await channel.assertQueue('dead_letters');
await channel.bindQueue('dead_letters', 'dlx', '#');

await channel.assertQueue('notifications_queue', {
  durable: true,
  deadLetterExchange: 'dlx' // Route failed messages here
});

Distributed logging is essential. We use Winston with a correlation ID injector:

// shared/logger.ts
export const logger = winston.createLogger({
  format: winston.format.combine(
    winston.format((info) => {
      info.correlationId = cls.get('correlationId');
      return info;
    })()
  ),
  transports: [new winston.transports.Console()]
});

Health checks keep our services observable:

// user-service/src/health/health.controller.ts
@Get('health')
@HealthCheck()
checkHealth() {
  return this.health.check([
    () => this.db.pingCheck('database'),
    () => this.rabbit.pingCheck('rabbitmq')
  ]);
}

Our Docker Compose ties everything together:

# docker-compose.yml
services:
  rabbitmq:
    image: rabbitmq:3-management
    ports:
      - "5672:5672"
      - "15672:15672"

  user-service:
    build: ./services/user-service
    depends_on:
      rabbitmq:
        condition: service_healthy

  # Health check for RabbitMQ
  healthcheck:
    test: rabbitmq-diagnostics check_port_connectivity
    interval: 30s

Testing is crucial. We use Jest to verify event flows:

// Notification service test
it('sends welcome email on USER_CREATED', async () => {
  mockEmailService.sendWelcome.mockResolvedValue(true);
  
  await eventBus.publish('user.created', {
    eventType: 'USER_CREATED',
    data: { email: '[email protected]' }
  });

  await new Promise(resolve => setTimeout(resolve, 100));
  expect(mockEmailService.sendWelcome).toHaveBeenCalled();
});

Performance tips from production: Prefetch limits prevent consumer overload. Set channel.prefetch(20) in RabbitMQ consumers. Use connection pooling for databases. Enable gzip compression in NestJS with app.use(compression()).

Common pitfalls? Forgetting to handle duplicate events. Services must be idempotent. Include idempotencyKey in events and check it before processing. Another gotcha - not setting message TTLs. Without them, failed messages might retry indefinitely.

What challenges have you faced with microservices? I’d love to hear your solutions. If this guide helped, share it with others building distributed systems!

Keywords: NestJS microservices, event-driven architecture, RabbitMQ message broker, Docker microservices, microservices with TypeScript, production microservices, distributed systems logging, microservices monitoring, Docker Compose deployment, dead letter queue implementation



Similar Posts
Blog Image
Build Complete Task Queue System with BullMQ Redis Node.js: Job Processing, Monitoring, Production Deploy

Learn to build a complete task queue system with BullMQ and Redis in Node.js. Master job processing, error handling, monitoring, and production deployment for scalable applications.

Blog Image
Build Complete Multi-Tenant SaaS API with NestJS Prisma PostgreSQL Row-Level Security Tutorial

Learn to build a secure multi-tenant SaaS API using NestJS, Prisma & PostgreSQL Row-Level Security. Complete guide with tenant isolation, authentication & performance optimization.

Blog Image
Event Sourcing with Node.js, TypeScript & PostgreSQL: Complete Implementation Guide 2024

Master Event Sourcing with Node.js, TypeScript & PostgreSQL. Learn to build event stores, handle aggregates, implement projections, and manage concurrency. Complete tutorial with practical examples.

Blog Image
Complete Guide to Next.js Prisma Integration: Build Type-Safe Database-Driven Apps in 2024

Learn how to integrate Next.js with Prisma ORM for type-safe, database-driven web apps. Build powerful full-stack applications with seamless frontend-backend unity.

Blog Image
Build Multi-Tenant SaaS Apps with NestJS, Prisma and PostgreSQL Row-Level Security

Learn to build scalable multi-tenant SaaS apps with NestJS, Prisma & PostgreSQL RLS. Complete guide with authentication, tenant isolation & optimization tips.

Blog Image
Build Multi-Tenant SaaS Applications with NestJS, Prisma, and PostgreSQL Row-Level Security

Learn to build scalable multi-tenant SaaS apps with NestJS, Prisma, and PostgreSQL RLS. Complete guide with secure tenant isolation and database-level security. Start building today!