Destino Dev

Blog

NestJS: criar e testar microservice TCP e gateway HTTP passo a passo

Por John Pires · 19 de março de 2026

NestJS: criar e testar microservice TCP e gateway HTTP passo a passo

Contexto: quando você vai usar isso

O problema que resolvemos

Microservices separam responsabilidades: um serviço calcula, outro expõe HTTP e autentica, outro persiste. No exemplo que acompanha este artigo vamos criar um microservice TCP responsável por lógica de cálculo (somar números) e um gateway HTTP que recebe requisições REST e delega essas operações pelo protocolo TCP.

Analogia simples

Imagine uma cozinha (microservice) que sabe cozinhar pratos e um garçom (gateway) que recebe pedidos do cliente. O garçom não cozinha — ele só traduz o pedido (HTTP) para a cozinha (TCP) e retorna o prato pronto. Essa separação é útil quando você quer escalar a cozinha de forma independente.

Este fluxo é comum em sistemas que exigem isolamento de responsabilidades, escalabilidade independente e comunicação eficiente dentro da mesma rede. Usaremos TCP porque é simples, eficiente e bem suportado pelo framework NestJS.

Explicação visual com diagramas mentais

Visão geral de alto nível

Antes de escrever uma linha de código, aprenda a imagem mental: cliente -> gateway HTTP -> microservice TCP -> gateway -> cliente. O gateway traduz HTTP/JSON para mensagens RPC sobre TCP com um padrão (cmd) e aguarda a resposta.

Fluxo de mensagens

Representação simples em ASCII (útil para memorizar):

Client (HTTP GET /math/sum?a=1&b=2)
  |
  v
Gateway HTTP (NestJS) --send({cmd: 'sum'}, [1,2])--> Microservice TCP (NestJS)
  <-- response (3) -----------------------------------------
  |
  v
HTTP Response 200 {result: 3}

Essa imagem ajuda a lembrar que o gateway não chama funções locais; ele envia mensagens que o microservice interpreta com @MessagePattern.

Primeiro exemplo simples

Estrutura de projeto mínima

Vamos montar dois pequenos projetos: microservice (porta TCP 8877) e gateway HTTP (porta 3000). Não há banco, autenticação ou validação complexa — foco em comunicação. A estrutura mínima para cada projeto:

  • microservice/
    • src/main.ts
    • src/app.module.ts
    • src/math.controller.ts
  • gateway/
    • src/main.ts
    • src/app.module.ts
    • src/app.controller.ts

Microservice: primeira versão (errada comum)

Errado: assumir que controllers do Nest MVC funcionam do mesmo jeito em microservices, ou esquecer de usar @MessagePattern. Exemplo equivocado: tentar usar @Get em um microservice TCP. Isso não funciona porque o microservice não expõe HTTP.

// ERRADO (microservice/src/math.controller.ts) - não-funcional para TCP
import { Controller, Get } from '@nestjs/common';

@Controller()
export class MathController {
  @Get('sum')
  sum() {
    return { result: 42 };
  }
}

O código acima parece correto para um app HTTP, mas é inútil em um microservice que usa Transport.TCP. O Nest ignora rotas HTTP quando você cria a aplicação com createMicroservice.

Microservice: versão correta

Abaixo está a implementação funcional do microservice. Ele responde a padrões de mensagem com um objeto que descreve o comando. Observe o uso de @MessagePattern e a assinatura que recebe dados serializados.

// microservice/src/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: 8877 },
  });
  await app.listen();
  console.log('Microservice TCP rodando na porta 8877');
}
bootstrap();

O bootstrap acima cria um microservice TCP. Sem esse passo você não terá um servidor TCP escutando conexões. Agora o controller:

// microservice/src/math.controller.ts
import { Controller } from '@nestjs/common';
import { MessagePattern } from '@nestjs/microservices';

@Controller()
export class MathController {
  @MessagePattern({ cmd: 'sum' })
  sum(numbers: number[]): number {
    // versão simples e síncrona: soma o array
    return numbers.reduce((a, b) => a + b, 0);
  }
}

Explicação: quando o microservice receber uma mensagem com padrão {cmd: 'sum'}, o método sum é invocado com os dados enviados (expect arrays de números).

Segundo exemplo mais realista

Gateway HTTP: implementação direta

A seguir implementamos o gateway HTTP que cria um ClientProxy para o microservice TCP. Aqui mostramos a forma correta de enviar a mensagem e aguardar a resposta usando rxjs firstValueFrom (em vez de toPromise que está depreciado).

// gateway/src/app.controller.ts
import { Controller, Get, Query } from '@nestjs/common';
import { ClientProxy, ClientProxyFactory, Transport } from '@nestjs/microservices';
import { firstValueFrom } from 'rxjs';

@Controller('math')
export class AppController {
  private client: ClientProxy;

  constructor() {
    this.client = ClientProxyFactory.create({
      transport: Transport.TCP,
      options: { host: '127.0.0.1', port: 8877 },
    });
  }

  @Get('sum')
  async sum(@Query('a') a: string, @Query('b') b: string) {
    const nums = [Number(a), Number(b)];
    // envia a mensagem e aguarda a resposta do microservice
    const result = await firstValueFrom(this.client.send<number>({ cmd: 'sum' }, nums));
    return { result };
  }
}

Contexto: o gateway recebe dois parâmetros de query, monta um array e delega a soma ao microservice via TCP. A resposta é enviada de volta como JSON.

Gateway: bootstrap e integração

Bootstrap do gateway é um app HTTP normal:

// gateway/src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);
  console.log('Gateway HTTP rodando na porta 3000');
}
bootstrap();

Depois de executar o microservice e o gateway, uma requisição GET http://localhost:3000/math/sum?a=2&b=3 deve responder { "result": 5 }.

Testes: como verificar sem ambos serviços no ar

Teste unitário do controller do gateway (errado x certo)

Errado: iniciar o microservice real durante testes unitários. Isso torna o teste lento e frágil. Em vez disso, deve-se mockar o ClientProxy.

// ERRADO: teste que tenta iniciar o microservice (não recomendado)
// ... (não colocamos código: esse é o antipadrão) 

Certo: mockar o ClientProxy com um objeto que tenha send retornando um Observable. Abaixo um teste usando Jest que injeta um mockProvider.

// gateway/test/app.controller.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { AppController } from '../src/app.controller';
import { of } from 'rxjs';

describe('AppController (unit)', () => {
  let controller: AppController;
  const mockClient = {
    send: jest.fn().mockImplementation(() => of(5)),
  };

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      controllers: [AppController],
    })
      .overrideProvider('CLIENT_PROXY')
      .useValue(mockClient)
      .compile();

    // instanciamos manualmente o controller com o mock
    controller = new AppController();
    // substituir client interno por mock
    (controller as any).client = mockClient;
  });

  it('should return sum result from microservice', async () => {
    const res = await controller.sum('2', '3');
    expect(res).toEqual({ result: 5 });
    expect(mockClient.send).toHaveBeenCalledWith({ cmd: 'sum' }, [2, 3]);
  });
});

Explanação: o teste cria um stub para send retornando um observable com valor 5. Não precisamos do microservice real. Assim os testes ficam rápidos e determinísticos.

Teste e2e (opcional) com ambos serviços

Quando precisar de integração real, crie testes e2e que iniciem ambos os apps em portas temporárias e façam requisições HTTP ao gateway. Esses testes são úteis para validar contratos, porém devem rodar em pipelines específicos (integração) e não em todos os commits rápidos.

Tip: mantenha testes unitários rápidos e isole dependências externas com mocks; reserve testes de integração para pipelines separados.

Erros comuns de iniciante e como evitar

Esquecer await/firstValueFrom

Um erro frequente: chamar client.send e retornar o Observable sem converter/await. Isso gera respostas incompletas ou objetos Observable expostos para o cliente HTTP. Use firstValueFrom ou toPromise (com cuidado) para transformar Observable em Promise.

Antipadrão: acoplar lógica no gateway

Errado: copiar a lógica de negócio para o gateway porque "é mais rápido na implementação". Isso destrói a separação de responsabilidades. Mantenha o gateway fino: validações básicas, autenticação e encaminhamento. Lógica complexa deve ficar no microservice.

Portas, host e firewalls

Problemas de rede: microservice não escuta nas interfaces esperadas ou firewall bloqueando porta. Durante desenvolvimento use 127.0.0.1/localhost. Em produção, prefira networking controlado (VPN, VPC) e variáveis de ambiente para hospedar IP/porta.

Serialização e tipos

O Nest serializa/deserializa dados automaticamente via JSON. Garanta que os dados enviados sejam serializáveis. Evite enviar funções, classes complexas ou Buffers sem conversão apropriada.

  • Não enviar objetos com métodos
  • Enviar arrays e objetos simples
  • Usar DTOs para documentar formato

Erros de exemplo: antes e depois (mini code review)

Antes: enviar Observable sem aguardar

Código que retorna o Observable diretamente do controller. Resultado: o Nest converte o Observable como resposta assíncrona e pode até funcionar, mas é frágil e confuso para iniciantes.

// ANTES (antipadrão)
@Get('sum')
sum(@Query('a') a, @Query('b') b) {
  const nums = [Number(a), Number(b)];
  return this.client.send<number>({ cmd: 'sum' }, nums); // retorna Observable
}

Embora Nest trate Observables em controllers HTTP, essa prática mistura paradigmas e dificulta tratamento de erros. Prefira converter explicitamente para Promise para controle fino.

Depois: aguardar com firstValueFrom

Correção clara, explícita e robusta:

// DEPOIS (recomendado)
@Get('sum')
async sum(@Query('a') a, @Query('b') b) {
  const nums = [Number(a), Number(b)];
  const result = await firstValueFrom(this.client.send<number>({ cmd: 'sum' }, nums));
  return { result };
}

Esse padrão facilita captura de exceções com try/catch e integração com pipes/guards do Nest.

Conclusão prática

Resumo objetivo

Você aprendeu as diferenças entre uma aplicação HTTP e um microservice TCP no NestJS, viu o antipadrão (usar @Get no microservice, retornar Observable sem controle, iniciar microservices em testes unitários) e as correções recomendadas: usar @MessagePattern, firstValueFrom no gateway e mockar ClientProxy nos testes.

Próximos passos e checklist rápido

Checklist prático para aplicar em projetos reais:

  1. Separar responsabilidades: gateway fino, microservice com lógica.
  2. Usar Transport.TCP para comunicação simples e performática em rede privada.
  3. Converter Observables com firstValueFrom antes de retornar em endpoints HTTP.
  4. Mockar ClientProxy nos testes unitários; rodar testes de integração separadamente.

Aplicando essas práticas você terá serviços menores, testes rápidos e uma arquitetura mais previsível.

Resumo final: não copie padrões HTTP para microservices; entenda o papel de cada camada e teste isoladamente.

Se precisar, posso gerar um repositório base com scripts de start (npm scripts) e arquivos docker-compose para levantar gateway e microservice juntos, além de exemplos de testes e2e prontos para rodar.

Assuntos: NestJS, microservices, tcp, Testes, gateway

Leia também