Executando verificação de segurança...
3

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:

  1. API Gateway (porta 3000) - recebe HTTP
  2. User Service (porta 3001) - autenticação
  3. Order Service (porta 3002) - pedidos
  4. 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

  1. Gateway envia: client.send({ cmd: 'create_order' }, data)
  2. TCP serializa JSON
  3. Envia para porta do microserviço
  4. Microserviço processa
  5. Resposta volta via TCP
  6. 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!

Carregando publicação patrocinada...