NestJS Microservices Architecture
NestJS provides first-class support for microservices via transport layers — from TCP to message queues.
Setting Up a Microservice
npm install @nestjs/microservices
// main.ts - Microservice bootstrap
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('Microservice is listening on port 3001');
}
bootstrap();
Message Patterns
// users.controller.ts
import { Controller } from '@nestjs/common';
import { MessagePattern, Payload } from '@nestjs/microservices';
import { UsersService } from './users.service';
@Controller()
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@MessagePattern({ cmd: 'get_user' })
async getUser(@Payload() data: { id: string }) {
return this.usersService.findById(data.id);
}
@MessagePattern({ cmd: 'create_user' })
async createUser(@Payload() createUserDto: CreateUserDto) {
return this.usersService.create(createUserDto);
}
}
RabbitMQ Transport
// app.module.ts - RabbitMQ setup
import { Module } from '@nestjs/common';
import { ClientsModule, Transport } from '@nestjs/microservices';
@Module({
imports: [
ClientsModule.register([
{
name: 'ORDERS_SERVICE',
transport: Transport.RMQ,
options: {
urls: ['amqp://localhost:5672'],
queue: 'orders_queue',
queueOptions: { durable: false },
},
},
]),
],
})
export class AppModule {}
// Sending messages from a client
import { Inject } from '@nestjs/common';
import { ClientProxy } from '@nestjs/microservices';
export class OrdersService {
constructor(
@Inject('ORDERS_SERVICE') private ordersClient: ClientProxy,
) {}
async createOrder(dto: CreateOrderDto) {
return this.ordersClient.send({ cmd: 'create_order' }, dto).toPromise();
}
async emitOrderCreated(order: Order) {
this.ordersClient.emit('order_created', order);
}
}
gRPC Transport
// main.ts - gRPC microservice
const app = await NestFactory.createMicroservice<MicroserviceOptions>(
AppModule,
{
transport: Transport.GRPC,
options: {
package: 'users',
protoPath: join(__dirname, './users.proto'),
url: 'localhost:5000',
},
},
);
// users.proto
syntax = "proto3";
package users;
service UsersService {
rpc FindOne (UserById) returns (User);
rpc FindAll (Empty) returns (UsersResponse);
}
message UserById {
int32 id = 1;
}
message User {
int32 id = 1;
string name = 2;
string email = 3;
}
Event-Driven with Redis Pub/Sub
// Using Redis for event-driven architecture
const app = await NestFactory.createMicroservice<MicroserviceOptions>(
AppModule,
{
transport: Transport.REDIS,
options: {
host: 'localhost',
port: 6379,
},
},
);
Exception Filters in Microservices
import { Catch, RpcExceptionFilter } from '@nestjs/common';
import { Observable, throwError } from 'rxjs';
import { RpcException } from '@nestjs/microservices';
@Catch(RpcException)
export class ExceptionFilter implements RpcExceptionFilter<RpcException> {
catch(exception: RpcException): Observable<any> {
return throwError(() => exception.getError());
}
}
Health Checks
import { HealthCheckService, MicroserviceHealthIndicator } from '@nestjs/terminus';
import { Transport } from '@nestjs/microservices';
@Controller('health')
export class HealthController {
constructor(
private health: HealthCheckService,
private microservice: MicroserviceHealthIndicator,
) {}
@Get()
@HealthCheck()
check() {
return this.health.check([
() =>
this.microservice.pingCheck('orders-service', {
transport: Transport.TCP,
options: { host: 'orders-service', port: 3001 },
}),
]);
}
}
Inter-Service Communication Patterns
Request-Response
Use send() for synchronous operations expecting a reply.
Fire-and-Forget
Use emit() for events where no response is needed.
Sagas (Distributed Transactions)
Implement the Saga pattern for multi-service transactions:
// Order saga: debit account -> reserve inventory -> confirm order
async createOrderSaga(orderId: string) {
try {
await this.paymentService.debit(orderId);
await this.inventoryService.reserve(orderId);
await this.ordersService.confirm(orderId);
} catch (err) {
// Compensating transactions
await this.inventoryService.release(orderId);
await this.paymentService.refund(orderId);
throw err;
}
}
Best Practices
- Use DTOs + class-validator for all inter-service messages
- Add correlation IDs to trace requests across services
- Implement circuit breakers with libraries like
opossum - Version your APIs to allow rolling updates
- Use shared libraries for common DTOs and event types