Como Criar um API Gateway com NestJS e Microserviços TCP: Guia Completo para Iniciantes
Como Criar um API Gateway com NestJS e Microserviços TCP
Se você está começando com microserviços em NestJS e quer entender como criar um API Gateway que se comunica com vários serviços via TCP, este artigo vai te mostrar o passo a passo completo. Vou te ensinar como construir uma arquitetura real com 3 microserviços: autenticação, pedidos e pagamentos.
O Que Você Vai Aprender
- O que é um API Gateway e por que usar em microserviços
- Como configurar múltiplos microserviços NestJS com TCP
- Implementação de autenticação JWT
- Comunicação entre Gateway e microserviços com MessagePattern
- Boas práticas para produção
Tempo: 30-40 minutos | Nível: Iniciante/Intermediário
Por Que Usar API Gateway?
Imagine que você tem 10 microserviços. Sem um Gateway, o front-end precisaria conhecer a URL e porta de cada um. Isso seria um pesadelo!
Problemas sem Gateway:
- Cliente precisa conhecer múltiplos endpoints
- Autenticação duplicada em cada serviço
- Difícil gerenciar versões
- Problemas de CORS
- Segurança comprometida
Benefícios do Gateway:
- Ponto único de entrada
- Autenticação centralizada
- Roteamento inteligente
- Simplifica o cliente
- Maior segurança
Arquitetura do Projeto
Vamos construir 4 apps NestJS:
- API Gateway (porta 3000) - recebe HTTP
- User Service (porta 3001) - autenticação
- Order Service (porta 3002) - pedidos
- Payment Service (porta 3003) - pagamentos
Fluxo: Cliente → Gateway (HTTP) → Microserviços (TCP)
Pré-requisitos
- Node.js 16+
- npm ou yarn
- NestJS CLI:
npm install -g @nestjs/cli
Passo 1: Criar Projetos
mkdir nestjs-microservices && cd nestjs-microservices
nest new api-gateway
nest new user-service
nest new order-service
nest new payment-service
Passo 2: Instalar Dependências
Em cada pasta:
npm install @nestjs/microservices
Passo 3: User Service
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: '127.0.0.1', port: 3001 },
});
await app.listen();
console.log('User Service on port 3001');
}
bootstrap();
app.controller.ts
import { Controller } from '@nestjs/common';
import { MessagePattern } from '@nestjs/microservices';
interface User {
id: number;
name: string;
email: string;
password: string;
}
@Controller()
export class AppController {
private users: User[] = [
{ id: 1, name: 'João Silva', email: '[email protected]', password: '123456' },
];
@MessagePattern({ cmd: 'get_users' })
getUsers() {
return this.users.map(({ password, ...user }) => user);
}
@MessagePattern({ cmd: 'create_user' })
createUser(data: Omit<User, 'id'>) {
if (this.users.find(u => u.email === data.email)) {
return { error: 'Email já existe' };
}
const user = { id: this.users.length + 1, ...data };
this.users.push(user);
const { password, ...result } = user;
return result;
}
@MessagePattern({ cmd: 'login' })
login(data: { email: string; password: string }) {
const user = this.users.find(u => u.email === data.email && u.password === data.password);
if (!user) return { error: 'Credenciais inválidas' };
const token = `fake-jwt-${user.id}-${Date.now()}`;
const { password, ...userSafe } = user;
return { token, user: userSafe };
}
@MessagePattern({ cmd: 'validate_token' })
validateToken(data: { token: string }) {
if (data.token?.startsWith('fake-jwt-')) {
const userId = parseInt(data.token.split('-')[2]);
return { valid: true, userId };
}
return { valid: false };
}
}
Rode: npm run start:dev
Passo 4: Order Service
main.ts (porta 3002)
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: '127.0.0.1', port: 3002 },
});
await app.listen();
console.log('Order Service on port 3002');
}
bootstrap();
app.controller.ts
import { Controller } from '@nestjs/common';
import { MessagePattern } from '@nestjs/microservices';
interface Order {
id: number;
userId: number;
product: string;
quantity: number;
totalPrice: number;
status: 'pending' | 'paid' | 'cancelled';
}
@Controller()
export class AppController {
private orders: Order[] = [];
@MessagePattern({ cmd: 'create_order' })
createOrder(data: any) {
const order = {
id: this.orders.length + 1,
...data,
status: 'pending' as const,
};
this.orders.push(order);
return order;
}
@MessagePattern({ cmd: 'get_orders' })
getOrders() {
return this.orders;
}
@MessagePattern({ cmd: 'get_order_by_id' })
getOrderById(data: { id: number }) {
const order = this.orders.find(o => o.id === data.id);
return order || { error: 'Pedido não encontrado' };
}
@MessagePattern({ cmd: 'update_order_status' })
updateStatus(data: { orderId: number; status: string }) {
const order = this.orders.find(o => o.id === data.orderId);
if (!order) return { error: 'Pedido não encontrado' };
order.status = data.status as any;
return order;
}
}
Rode: npm run start:dev
Passo 5: Payment Service
main.ts (porta 3003)
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: '127.0.0.1', port: 3003 },
});
await app.listen();
console.log('Payment Service on port 3003');
}
bootstrap();
app.controller.ts
import { Controller } from '@nestjs/common';
import { MessagePattern } from '@nestjs/microservices';
interface Payment {
id: number;
orderId: number;
amount: number;
status: 'approved' | 'rejected';
transactionId: string;
}
@Controller()
export class AppController {
private payments: Payment[] = [];
@MessagePattern({ cmd: 'process_payment' })
processPayment(data: { orderId: number; amount: number }) {
const approved = Math.random() > 0.2; // 80% aprovação
const payment = {
id: this.payments.length + 1,
orderId: data.orderId,
amount: data.amount,
status: approved ? 'approved' as const : 'rejected' as const,
transactionId: `TXN-${Date.now()}`,
};
this.payments.push(payment);
return payment;
}
@MessagePattern({ cmd: 'get_payment_by_order' })
getPayment(data: { orderId: number }) {
const payment = this.payments.find(p => p.orderId === data.orderId);
return payment || { error: 'Pagamento não encontrado' };
}
}
Rode: npm run start:dev
Passo 6: API Gateway
main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.enableCors();
await app.listen(3000);
console.log('Gateway on http://localhost:3000');
}
bootstrap();
app.module.ts
import { Module } from '@nestjs/common';
import { ClientsModule, Transport } from '@nestjs/microservices';
import { AppController } from './app.controller';
@Module({
imports: [
ClientsModule.register([
{
name: 'USER_SERVICE',
transport: Transport.TCP,
options: { host: '127.0.0.1', port: 3001 },
},
{
name: 'ORDER_SERVICE',
transport: Transport.TCP,
options: { host: '127.0.0.1', port: 3002 },
},
{
name: 'PAYMENT_SERVICE',
transport: Transport.TCP,
options: { host: '127.0.0.1', port: 3003 },
},
]),
],
controllers: [AppController],
})
export class AppModule {}
app.controller.ts
import { Controller, Post, Get, Body, Param, Inject, HttpException, HttpStatus, Headers } from '@nestjs/common';
import { ClientProxy } from '@nestjs/microservices';
import { firstValueFrom } from 'rxjs';
@Controller('api')
export class AppController {
constructor(
@Inject('USER_SERVICE') private userClient: ClientProxy,
@Inject('ORDER_SERVICE') private orderClient: ClientProxy,
@Inject('PAYMENT_SERVICE') private paymentClient: ClientProxy,
) {}
@Post('users/register')
async register(@Body() data: any) {
const result = await firstValueFrom(this.userClient.send({ cmd: 'create_user' }, data));
if (result.error) throw new HttpException(result.error, HttpStatus.BAD_REQUEST);
return { message: 'Usuário criado', data: result };
}
@Post('users/login')
async login(@Body() data: any) {
const result = await firstValueFrom(this.userClient.send({ cmd: 'login' }, data));
if (result.error) throw new HttpException(result.error, HttpStatus.UNAUTHORIZED);
return { message: 'Login OK', data: result };
}
@Post('orders')
async createOrder(@Body() data: any, @Headers('authorization') auth: string) {
const tokenData = await this.validateToken(auth);
data.userId = tokenData.userId;
const order = await firstValueFrom(this.orderClient.send({ cmd: 'create_order' }, data));
return { message: 'Pedido criado', data: order };
}
@Get('orders/:id')
async getOrder(@Param('id') id: string, @Headers('authorization') auth: string) {
await this.validateToken(auth);
const order = await firstValueFrom(this.orderClient.send({ cmd: 'get_order_by_id' }, { id: +id }));
if (order.error) throw new HttpException(order.error, HttpStatus.NOT_FOUND);
return { data: order };
}
@Post('payments/process')
async processPayment(@Body() data: any, @Headers('authorization') auth: string) {
await this.validateToken(auth);
const payment = await firstValueFrom(this.paymentClient.send({ cmd: 'process_payment' }, data));
if (payment.status === 'approved') {
await firstValueFrom(
this.orderClient.send({ cmd: 'update_order_status' }, { orderId: data.orderId, status: 'paid' })
);
}
return { message: `Pagamento ${payment.status}`, data: payment };
}
private async validateToken(authHeader: string) {
if (!authHeader) throw new HttpException('Token não fornecido', HttpStatus.UNAUTHORIZED);
const token = authHeader.replace('Bearer ', '');
const validation = await firstValueFrom(this.userClient.send({ cmd: 'validate_token' }, { token }));
if (!validation.valid) throw new HttpException('Token inválido', HttpStatus.UNAUTHORIZED);
return validation;
}
}
Rode: npm run start:dev
Testando a Aplicação
1. Registrar usuário
curl -X POST http://localhost:3000/api/users/register \
-H "Content-Type: application/json" \
-d '{"name":"Pedro","email":"[email protected]","password":"123"}'
2. Login
curl -X POST http://localhost:3000/api/users/login \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]","password":"123"}'
Copie o token retornado!
3. Criar pedido
curl -X POST http://localhost:3000/api/orders \
-H "Content-Type: application/json" \
-H "Authorization: Bearer SEU_TOKEN" \
-d '{"product":"Notebook","quantity":1,"totalPrice":3500}'
4. Processar pagamento
curl -X POST http://localhost:3000/api/payments/process \
-H "Content-Type: application/json" \
-H "Authorization: Bearer SEU_TOKEN" \
-d '{"orderId":1,"amount":3500}'
5. Verificar pedido
curl http://localhost:3000/api/orders/1 \
-H "Authorization: Bearer SEU_TOKEN"
O status do pedido mudou para "paid" automaticamente!
Entendendo a Comunicação TCP
MessagePattern vs EventPattern
MessagePattern (Request/Response)
@MessagePattern({ cmd: 'get_user' })
getUser(data) {
return this.users.find(u => u.id === data.id);
}
Use quando precisa de resposta.
EventPattern (Fire and Forget)
@EventPattern('user_created')
handleUserCreated(data) {
this.sendWelcomeEmail(data.userId);
}
Use para eventos assíncronos.
Fluxo TCP
- Gateway envia:
client.send({ cmd: 'create_order' }, data) - TCP serializa JSON
- Envia para porta do microserviço
- Microserviço processa
- Resposta volta via TCP
- Gateway recebe com
firstValueFrom()
Melhorias para Produção
1. JWT Real
npm install @nestjs/jwt bcrypt
2. Banco de Dados
npm install @nestjs/typeorm typeorm pg
3. Validação
npm install class-validator class-transformer
import { IsString, IsEmail, MinLength } from 'class-validator';
export class CreateUserDto {
@IsString()
@MinLength(3)
name: string;
@IsEmail()
email: string;
@IsString()
@MinLength(6)
password: string;
}
4. Retry e Timeout
ClientsModule.register([{
name: 'USER_SERVICE',
transport: Transport.TCP,
options: {
host: '127.0.0.1',
port: 3001,
retryAttempts: 5,
retryDelay: 3000,
},
}])
5. Docker
version: '3.8'
services:
api-gateway:
build: ./api-gateway
ports: ["3000:3000"]
user-service:
build: ./user-service
ports: ["3001:3001"]
order-service:
build: ./order-service
ports: ["3002:3002"]
payment-service:
build: ./payment-service
ports: ["3003:3003"]
Protocolos: TCP vs Outros
TCP
- ✅ Confiável e ordenado
- ✅ Simples
- ❌ Não é o mais rápido
gRPC
- ✅ Muito rápido
- ✅ Streaming
- ❌ Mais complexo
Redis
- ✅ Pub/sub nativo
- ✅ Cache
- ❌ Requer Redis
RabbitMQ/Kafka
- ✅ Alta escala
- ✅ Mensagens persistentes
- ❌ Infraestrutura complexa
Troubleshooting
"Cannot connect"
- Verifique se serviços estão rodando
- Teste:
telnet 127.0.0.1 3001
"Timeout exceeded"
- Aumente timeout
- Verifique logs
"No handler for pattern"
- Confirme @MessagePattern correto
- Use console.log para debug
Próximos Passos
✅ Adicionar mais microserviços
✅ Implementar CQRS
✅ Event Sourcing
✅ API Versioning
✅ GraphQL Gateway
✅ Kubernetes
Conclusão
Você aprendeu a criar um API Gateway completo com NestJS e microserviços TCP! Agora você pode:
✅ Estruturar apps escaláveis
✅ Separar responsabilidades
✅ Autenticação centralizada
✅ Comunicar serviços eficientemente
Lembre-se: Microserviços não são sempre a melhor solução. Para apps pequenos, monolitos são mais simples. Mas quando precisa escalar, microserviços são o caminho!