NestJS Microservices

A microservices architecture splits a large application into small, independent services that each handle one business domain. An e-commerce platform might have separate services for users, orders, payments, and inventory. Each service runs independently, scales independently, and communicates with others through a message transport — TCP, Redis, RabbitMQ, Kafka, or NATS.

Monolith vs Microservices

Monolith (single NestJS app):
  ┌─────────────────────────────────────┐
  │ Users | Orders | Payments | Products│
  │    All in one process               │
  └─────────────────────────────────────┘

Microservices (multiple NestJS apps):
  ┌──────────┐   ┌──────────┐   ┌──────────┐
  │  Users   │   │  Orders  │   │ Payments │
  │ Service  │   │ Service  │   │ Service  │
  └────┬─────┘   └────┬─────┘   └────┬─────┘
       │              │              │
       └──────────────┴──────────────┘
               Message Transport
              (Redis / RabbitMQ / TCP)

Installing the Microservices Package

npm install @nestjs/microservices

Creating a Microservice

A microservice listens for messages instead of HTTP requests. Bootstrap it with createMicroservice instead of create:

// main.ts (orders microservice)
import { NestFactory } from '@nestjs/core';
import { MicroserviceOptions, Transport } from '@nestjs/microservices';

async function bootstrap() {
  const app = await NestFactory.createMicroservice<MicroserviceOptions>(
    AppModule,
    {
      transport: Transport.TCP,
      options: { host: 'localhost', port: 3001 },
    },
  );
  await app.listen();
}
bootstrap();

Message Patterns

Instead of @Get() or @Post(), microservice controllers use @MessagePattern() to match incoming messages:

// orders.controller.ts (inside the Orders microservice)
import { Controller } from '@nestjs/common';
import { MessagePattern, Payload } from '@nestjs/microservices';

@Controller()
export class OrdersController {

  @MessagePattern({ cmd: 'get_order' })
  getOrder(@Payload() data: { id: number }) {
    return this.ordersService.findOne(data.id);
  }

  @MessagePattern({ cmd: 'create_order' })
  createOrder(@Payload() data: CreateOrderDto) {
    return this.ordersService.create(data);
  }
}

Client Proxy — Sending Messages Between Services

The main API gateway (or any service) sends messages to a microservice using a ClientProxy:

// In a gateway/API service that calls the Orders microservice
@Module({
  imports: [
    ClientsModule.register([
      {
        name: 'ORDERS_SERVICE',
        transport: Transport.TCP,
        options: { host: 'localhost', port: 3001 },
      },
    ]),
  ],
})
export class AppModule {}

// In the controller
@Controller('orders')
export class OrdersController {
  constructor(
    @Inject('ORDERS_SERVICE') private readonly ordersClient: ClientProxy,
  ) {}

  @Get(':id')
  getOrder(@Param('id', ParseIntPipe) id: number) {
    return this.ordersClient.send({ cmd: 'get_order' }, { id });
  }

  @Post()
  createOrder(@Body() dto: CreateOrderDto) {
    return this.ordersClient.send({ cmd: 'create_order' }, dto);
  }
}

send vs emit

client.send(pattern, data)   → Request-Response (waits for a reply)
client.emit(pattern, data)   → Event (fire and forget, no reply expected)

Use send when: you need a result back (fetch an order, create a user)
Use emit when: notifying other services (order placed, user registered)

Event-Based Communication

// Emitting an event from the Orders service when an order is placed
this.ordersClient.emit('order_placed', { orderId: 1, userId: 42 });

// The Payments service listens for this event
@EventPattern('order_placed')
async handleOrderPlaced(@Payload() data: { orderId: number; userId: number }) {
  await this.paymentsService.initiatePayment(data.orderId);
}

// The Inventory service also listens for the same event
@EventPattern('order_placed')
async reserveStock(@Payload() data: { orderId: number }) {
  await this.inventoryService.reserve(data.orderId);
}

Hybrid App — HTTP and Microservice Together

A common pattern is to run an HTTP server (for external clients) and a microservice transport (for internal service communication) in the same NestJS app:

const app = await NestFactory.create(AppModule);    // HTTP server

app.connectMicroservice<MicroserviceOptions>({
  transport: Transport.TCP,
  options: { port: 3001 },
});

await app.startAllMicroservices();
await app.listen(3000);

Available Transports

Transport.TCP       - Direct TCP socket (simple, no broker needed)
Transport.REDIS     - Redis pub/sub (easy setup, good for moderate scale)
Transport.RABBITMQ  - RabbitMQ message queue (reliable, durable messages)
Transport.KAFKA     - Apache Kafka (high throughput, event streaming)
Transport.NATS      - NATS messaging (fast, lightweight)
Transport.GRPC      - gRPC (typed, efficient binary protocol)

NestJS applies the same module, controller, guard, pipe, and interceptor patterns to microservices as it does to HTTP applications. The structural knowledge you build for REST APIs transfers directly to building distributed systems, making NestJS a coherent choice for scaling from a monolith to a microservices architecture.

Leave a Comment

Your email address will not be published. Required fields are marked *