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
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.
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.
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.
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