Microservices Introduction

Turn a Nest app into a message-driven service with transports, @MessagePattern, and @EventPattern.

Nest isn't only for HTTP. The same controllers, providers, and DI power microservices that communicate over TCP, Redis, NATS, RabbitMQ, or Kafka. You keep everything you know; only the transport changes.

Message vs event

  • @MessagePattern('get_user') - request/response. The caller waits for a reply. Use for queries and RPC-style calls.
  • @EventPattern('user.created') - fire-and-forget. No response; many services can react. Use for domain events.
@MessagePattern('sum')
sum(data: number[]): number {
  return data.reduce((a, b) => a + b, 0);
}

Creating a microservice

NestFactory.createMicroservice(AppModule, { transport, options }) starts a listener instead of an HTTP server. A single app can even be hybrid: serve HTTP and connect a microservice transport with app.connectMicroservice().

Calling another service

You talk to a microservice through a ClientProxy, injected via ClientsModule.register(...). .send(pattern, data) returns an Observable for request/response; .emit(pattern, data) fires an event. Because it's an Observable, it composes with the RxJS you already use in interceptors.

What stays the same, what changes

DI, modules, pipes, guards, interceptors, and filters all still work. What changes is the shape of a "request": there's no URL or HTTP status, so you reach data through ctx.switchToRpc() and errors travel as serialized payloads. Start with TCP locally, then pick a broker (NATS/Kafka) for production fan-out.

Example

Example · typescript
import { Controller, Injectable, Module } from '@nestjs/common';
import {
  MessagePattern,
  EventPattern,
  Payload,
  ClientProxy,
  ClientsModule,
  Transport,
} from '@nestjs/microservices';
import { firstValueFrom } from 'rxjs';

// ---- The service side ----
@Controller()
export class MathController {
  @MessagePattern('sum') // request/response
  accumulate(@Payload() nums: number[]): number {
    return nums.reduce((a, b) => a + b, 0);
  }

  @EventPattern('order.created') // fire-and-forget
  onOrderCreated(@Payload() data: { orderId: string }) {
    console.log(`reacting to order ${data.orderId}`);
  }
}

// ---- The caller side ----
@Module({
  imports: [
    ClientsModule.register([
      {
        name: 'MATH_SERVICE',
        transport: Transport.TCP,
        options: { host: '127.0.0.1', port: 3001 },
      },
    ]),
  ],
})
export class CallerModule {}

@Injectable()
export class CalculatorFacade {
  constructor(private readonly client: ClientProxy) {}

  // .send() -> Observable<number>; await the first value
  total(nums: number[]): Promise<number> {
    return firstValueFrom(this.client.send<number>('sum', nums));
  }

  notifyOrder(orderId: string) {
    this.client.emit('order.created', { orderId }); // no reply
  }
}

// Bootstrap a listener instead of an HTTP server:
// const app = await NestFactory.createMicroservice(AppModule, {
//   transport: Transport.TCP,
//   options: { port: 3001 },
// });
// await app.listen();

When to use it

  • A team extracts the notifications feature into a separate NestJS microservice that listens for messages over TCP and sends emails independently of the main API.
  • A developer uses @EventPattern("order.created") to react to order events from an NATS message broker without the notifications service knowing about the orders service.
  • An engineer uses ClientProxy.send() to make request-reply calls to a pricing microservice from the main API and await the response.

More examples

Create a TCP microservice

Boots a NestJS app as a TCP microservice on port 3001 using createMicroservice instead of the standard HTTP server.

Example · ts
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.TCP, options: { port: 3001 } },
  );
  await app.listen();
}
bootstrap();

Handle messages with @MessagePattern

Shows @MessagePattern for request-reply communication and @EventPattern for fire-and-forget event handling in a single microservice controller.

Example · ts
import { Controller } from '@nestjs/common';
import { MessagePattern, Payload } from '@nestjs/microservices';

@Controller()
export class MathController {
  @MessagePattern('math.add')   // handles request-reply
  add(@Payload() data: { a: number; b: number }): number {
    return data.a + data.b;
  }

  @EventPattern('order.created')  // fire-and-forget events
  handleOrderCreated(@Payload() data: { orderId: number }) {
    console.log('Order created:', data.orderId);
  }
}

Client proxy to call microservice

Creates a ClientProxy in the main API to send a request-reply message to the math microservice and return the observable result.

Example · ts
import { Injectable } from '@nestjs/common';
import { ClientProxy, ClientProxyFactory, Transport } from '@nestjs/microservices';

@Injectable()
export class ApiGatewayService {
  private client: ClientProxy = ClientProxyFactory.create({
    transport: Transport.TCP,
    options: { host: 'localhost', port: 3001 },
  });

  add(a: number, b: number) {
    return this.client.send<number>('math.add', { a, b });
  }
}

Discussion

  • Be the first to comment on this lesson.