Microservices Overview
A NestJS microservice is an application that communicates over a message-based protocol rather than the HTTP request/response cycle you get from the default platform. The @nestjs/microservices package gives you the same controllers, providers, modules, and dependency injection you already know, but swaps the underlying transport for something like TCP, Redis, NATS, RabbitMQ, Kafka, or gRPC. This means you write your business logic once and let Nest handle the wire format, serialization, and connection lifecycle behind a thin, consistent abstraction.
Why a separate microservices layer
The core @nestjs/platform-express (or Fastify) adapter is built around HTTP. Microservices, by contrast, are usually about asynchronous, low-overhead communication between services in a distributed system. Nest unifies both worlds: the programming model is identical, so the only thing that changes is how a message arrives and how a response leaves. That symmetry is the whole point — you should be able to move a handler from an HTTP controller to a microservice with minimal friction.
Install the package alongside the transport driver you intend to use:
npm install @nestjs/microservices
Transporters
A transporter is the strategy that encapsulates the protocol used to send and receive messages. Nest ships with several built-in transporters, and you select one when you create the microservice. Each transporter has its own connection options, but they all expose the same MessagePattern / EventPattern handler API.
| Transporter | Transport enum | Typical use case |
|---|---|---|
| TCP | Transport.TCP | Simple, dependency-free internal RPC |
| Redis | Transport.REDIS | Pub/sub style messaging via Redis |
| NATS | Transport.NATS | Lightweight, high-throughput messaging |
| RabbitMQ | Transport.RMQ | Reliable queues and acknowledgements |
| Kafka | Transport.KAFKA | Event streaming and log-based pipelines |
| gRPC | Transport.GRPC | Strongly-typed contracts via Protocol Buffers |
The server role
A microservice server listens for incoming messages and dispatches them to handlers. You bootstrap it with NestFactory.createMicroservice instead of NestFactory.create. The generic type argument carries the transport-specific options so they are fully type-checked.
// main.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: { host: '0.0.0.0', port: 3001 },
},
);
await app.listen();
console.log('Math microservice is listening on TCP 3001');
}
bootstrap();
Handlers live in ordinary controllers. The @MessagePattern decorator binds a method to a request/response interaction, while @EventPattern binds it to a fire-and-forget event.
// math.controller.ts
import { Controller } from '@nestjs/common';
import { MessagePattern, EventPattern, Payload } from '@nestjs/microservices';
@Controller()
export class MathController {
@MessagePattern({ cmd: 'sum' })
accumulate(@Payload() data: number[]): number {
return data.reduce((acc, value) => acc + value, 0);
}
@EventPattern('order_created')
handleOrderCreated(@Payload() payload: { orderId: string }) {
console.log(`Order received: ${payload.orderId}`);
}
}
The pattern passed to
@MessagePatterncan be a string, number, or object. Nest matches it by deep equality, so the client must send the exact same pattern shape to reach the handler.
The client role: ClientProxy
To call a microservice, you inject a ClientProxy. This is the abstraction that hides transport details from the caller — it exposes just two methods regardless of the underlying protocol:
send(pattern, data)returns an RxJSObservablefor request/response messaging (matched by@MessagePattern).emit(pattern, data)publishes an event with no expected reply (matched by@EventPattern).
Register the client in a module with ClientsModule, then inject it by token.
// app.module.ts
import { Module } from '@nestjs/common';
import { ClientsModule, Transport } from '@nestjs/microservices';
import { GatewayController } from './gateway.controller';
@Module({
imports: [
ClientsModule.register([
{
name: 'MATH_SERVICE',
transport: Transport.TCP,
options: { host: 'localhost', port: 3001 },
},
]),
],
controllers: [GatewayController],
})
export class AppModule {}
// gateway.controller.ts
import { Controller, Get, Inject } from '@nestjs/common';
import { ClientProxy } from '@nestjs/microservices';
import { Observable } from 'rxjs';
@Controller('math')
export class GatewayController {
constructor(@Inject('MATH_SERVICE') private readonly client: ClientProxy) {}
@Get('sum')
sum(): Observable<number> {
return this.client.send<number, number[]>({ cmd: 'sum' }, [1, 2, 3, 4]);
}
}
Hitting the HTTP gateway forwards the request over TCP to the math microservice and streams the reply back:
Output:
$ curl http://localhost:3000/math/sum
10
Because send() returns an Observable, Nest’s HTTP layer subscribes to it automatically when you return it from a route handler. The TCP frame, the JSON serialization, and the correlation between request and response are all managed for you.
How Nest abstracts the transport
The key idea is that ClientProxy and the @MessagePattern/@EventPattern handlers form a stable contract. Switching from TCP to Redis or NATS is mostly a configuration change in createMicroservice and ClientsModule.register — your controllers and services stay untouched. This lets you start with the zero-dependency TCP transport during development and graduate to a broker like RabbitMQ or Kafka in production without rewriting application logic.
Best practices
- Keep message patterns in a shared constants file so the server and client never drift out of sync.
- Use
@MessagePatternfor queries that need a reply and@EventPatternfor fire-and-forget notifications; do not block on events. - Prefer object patterns (
{ cmd: 'sum' }) over bare strings — they are self-documenting and easier to namespace. - Connect long-lived clients via the module’s
onApplicationBootstraplifecycle rather than per-request to avoid repeated handshakes. - Validate and type your payloads with DTOs and pipes just as you would for HTTP endpoints.
- Choose a broker-backed transporter (RabbitMQ, Kafka, NATS) when you need durability, retries, or back-pressure; reserve TCP for simple internal RPC.