Introduction
Microservices architecture has become the standard for building scalable, maintainable applications. NestJS, with its modular architecture and TypeScript support, is an excellent choice for building microservices with Node.js.
This guide covers everything you need to know about building production-ready microservices with NestJS.
What is NestJS?
NestJS is a progressive Node.js framework for building efficient, scalable server-side applications. It uses TypeScript by default and combines elements of OOP, FP, and FRP.
Why NestJS for Microservices?
- Modular Architecture - Easy to split into services
- Built-in Microservices Support - Multiple transport layers
- Dependency Injection - Clean, testable code
- TypeScript - Type safety and better tooling
- Extensive Ecosystem - Many official modules
Project Setup
Create a monorepo for your microservices:
# Install NestJS CLI
npm i -g @nestjs/cli
# Create monorepo
nest new microservices-demo
cd microservices-demo
# Generate apps
nest generate app user-service
nest generate app order-service
nest generate app api-gateway
# Install microservices packages
npm install @nestjs/microservices
npm install @nestjs/config class-validator class-transformer
API Gateway
The gateway routes requests to appropriate services:
// apps/api-gateway/src/main.ts
import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe({
whitelist: true,
transform: true,
}));
app.enableCors();
await app.listen(3000);
console.log('API Gateway running on port 3000');
}
bootstrap();
// apps/api-gateway/src/app.module.ts
import { Module } from '@nestjs/common';
import { ClientsModule, Transport } from '@nestjs/microservices';
import { UserController } from './controllers/user.controller';
import { OrderController } from './controllers/order.controller';
@Module({
imports: [
ClientsModule.register([
{
name: 'USER_SERVICE',
transport: Transport.TCP,
options: { host: 'localhost', port: 3001 },
},
{
name: 'ORDER_SERVICE',
transport: Transport.TCP,
options: { host: 'localhost', port: 3002 },
},
]),
],
controllers: [UserController, OrderController],
})
export class AppModule {}
User Service
// apps/user-service/src/main.ts
import { NestFactory } from '@nestjs/core';
import { Transport, MicroserviceOptions } from '@nestjs/microservices';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.createMicroservice<MicroserviceOptions>(
AppModule,
{
transport: Transport.TCP,
options: { host: 'localhost', port: 3001 },
},
);
await app.listen();
console.log('User Service running on port 3001');
}
bootstrap();
// apps/user-service/src/user.controller.ts
import { Controller } from '@nestjs/common';
import { MessagePattern, Payload } from '@nestjs/microservices';
import { UserService } from './user.service';
import { CreateUserDto } from './dto/create-user.dto';
@Controller()
export class UserController {
constructor(private readonly userService: UserService) {}
@MessagePattern({ cmd: 'create_user' })
async createUser(@Payload() data: CreateUserDto) {
return this.userService.create(data);
}
@MessagePattern({ cmd: 'get_user' })
async getUser(@Payload() id: string) {
return this.userService.findOne(id);
}
@MessagePattern({ cmd: 'get_all_users' })
async getAllUsers() {
return this.userService.findAll();
}
}
// apps/user-service/src/user.service.ts
import { Injectable } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
interface User {
id: string;
email: string;
name: string;
createdAt: Date;
}
@Injectable()
export class UserService {
private users: User[] = [];
create(data: CreateUserDto): User {
const user: User = {
id: crypto.randomUUID(),
...data,
createdAt: new Date(),
};
this.users.push(user);
return user;
}
findOne(id: string): User | undefined {
return this.users.find(user => user.id === id);
}
findAll(): User[] {
return this.users;
}
}
Gateway Controller
// apps/api-gateway/src/controllers/user.controller.ts
import { Controller, Get, Post, Body, Param, Inject } from '@nestjs/common';
import { ClientProxy } from '@nestjs/microservices';
import { firstValueFrom } from 'rxjs';
import { CreateUserDto } from '../dto/create-user.dto';
@Controller('users')
export class UserController {
constructor(
@Inject('USER_SERVICE') private readonly userClient: ClientProxy,
) {}
@Post()
async createUser(@Body() data: CreateUserDto) {
return firstValueFrom(
this.userClient.send({ cmd: 'create_user' }, data),
);
}
@Get(':id')
async getUser(@Param('id') id: string) {
return firstValueFrom(
this.userClient.send({ cmd: 'get_user' }, id),
);
}
@Get()
async getAllUsers() {
return firstValueFrom(
this.userClient.send({ cmd: 'get_all_users' }, {}),
);
}
}
Using RabbitMQ for Message Queue
For production, use a message broker like RabbitMQ:
npm install amqplib amqp-connection-manager
// Using RabbitMQ transport
ClientsModule.register([
{
name: 'USER_SERVICE',
transport: Transport.RMQ,
options: {
urls: ['amqp://localhost:5672'],
queue: 'user_queue',
queueOptions: { durable: true },
},
},
]);
// User service with RabbitMQ
const app = await NestFactory.createMicroservice<MicroserviceOptions>(
AppModule,
{
transport: Transport.RMQ,
options: {
urls: ['amqp://localhost:5672'],
queue: 'user_queue',
queueOptions: { durable: true },
},
},
);
Event-Driven Communication
Emit events between services:
// Emitting events
@Injectable()
export class OrderService {
constructor(
@Inject('USER_SERVICE') private readonly userClient: ClientProxy,
) {}
async createOrder(data: CreateOrderDto) {
const order = await this.orderRepository.save(data);
// Emit event to other services
this.userClient.emit('order_created', {
orderId: order.id,
userId: data.userId,
});
return order;
}
}
// Listening to events
@Controller()
export class UserController {
@EventPattern('order_created')
async handleOrderCreated(@Payload() data: { orderId: string; userId: string }) {
console.log(`User ${data.userId} created order ${data.orderId}`);
// Update user statistics, send notifications, etc.
}
}
Health Checks
Add health checks for monitoring:
// health.controller.ts
import { Controller, Get } from '@nestjs/common';
import {
HealthCheck,
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('user-service', {
transport: Transport.TCP,
options: { host: 'localhost', port: 3001 },
}),
() => this.microservice.pingCheck('order-service', {
transport: Transport.TCP,
options: { host: 'localhost', port: 3002 },
}),
]);
}
}
Docker Deployment
# Dockerfile for each service
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build user-service
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist/apps/user-service ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "dist/main.js"]
# docker-compose.yml
version: '3.8'
services:
api-gateway:
build:
context: .
dockerfile: apps/api-gateway/Dockerfile
ports:
- "3000:3000"
depends_on:
- user-service
- order-service
user-service:
build:
context: .
dockerfile: apps/user-service/Dockerfile
expose:
- "3001"
order-service:
build:
context: .
dockerfile: apps/order-service/Dockerfile
expose:
- "3002"
rabbitmq:
image: rabbitmq:3-management
ports:
- "5672:5672"
- "15672:15672"
Best Practices
- Service Discovery - Use Consul or Kubernetes for dynamic discovery
- Circuit Breaker - Implement with
@nestjs/terminus - Distributed Tracing - Use Jaeger or Zipkin
- Centralized Logging - Use ELK Stack or Datadog
- API Versioning - Plan for backward compatibility
Conclusion
NestJS provides excellent tooling for building microservices. Start with a simple setup and gradually add complexity as needed. Focus on service boundaries and communication patterns.
Ready to build your microservices architecture? Contact Space2Code for expert development services.
Tags
Need Help with Backend Development?
Our team of experts is ready to help you build your next project.
Get Free Consultation