Home > Blog > Desenvolvimento Web
Desenvolvimento Web
JavaScript
Programação

gRPC com Node.js: Guia Prático

Atualizado em: 20 de agosto de 2026

Rack de servidores processando fluxos de dados no Node.js

gRPC com Node.js permite criar serviços remotos com contratos definidos em Protocol Buffers. O cliente chama um método como se fosse uma função, enquanto a biblioteca cuida da serialização binária, HTTP/2, status, metadata e streaming. Essa abordagem é comum em comunicação interna entre serviços, especialmente quando desempenho, contratos multiplataforma e geração de clientes são importantes.

gRPC não elimina falhas de rede. Cada chamada precisa de deadline, tratamento de status, autenticação, autorização, limites e observabilidade. Também é necessário evoluir arquivos proto sem quebrar clientes antigos. Neste guia, você verá como criar um serviço, usar streaming, testar e preparar o sistema para produção. Para revisar a base, consulte o que é Node.js e o que é API.

Quando usar gRPC?

gRPC é indicado para comunicação serviço a serviço, SDKs internos, sistemas com várias linguagens e fluxos que se beneficiam de streaming. Para APIs públicas consumidas diretamente por navegadores, REST ou GraphQL podem ser mais simples, pois o suporte nativo do browser ao protocolo gRPC completo é limitado e costuma exigir gRPC-Web ou um gateway.

A escolha deve considerar debugging, infraestrutura, compatibilidade, experiência da equipe e necessidade real de geração de código. Não adote apenas porque o formato binário parece mais rápido; meça o fluxo completo.

Instalação

npm init -y
npm install @grpc/grpc-js @grpc/proto-loader
npm install --save-dev node:test

O pacote @grpc/grpc-js implementa gRPC em JavaScript sem extensão nativa. O proto-loader carrega definições dinamicamente. Outra estratégia é gerar código TypeScript no build, aumentando tipagem e reduzindo erros de nomes.

Definindo o contrato Proto

syntax = "proto3";

package orders.v1;

service OrdersService {
  rpc GetOrder(GetOrderRequest) returns (Order);
  rpc ListOrders(ListOrdersRequest) returns (stream Order);
  rpc CreateOrders(stream CreateOrderRequest)
    returns (CreateOrdersResponse);
}

message GetOrderRequest {
  string order_id = 1;
}

message ListOrdersRequest {
  string customer_id = 1;
  int32 page_size = 2;
}

message Order {
  string id = 1;
  string customer_id = 2;
  int64 total_cents = 3;
  string status = 4;
  string created_at = 5;
}

message CreateOrderRequest {
  string customer_id = 1;
  int64 total_cents = 2;
}

message CreateOrdersResponse {
  int32 created = 1;
}

Os números dos campos fazem parte do contrato. Nunca reutilize um número removido para outro significado. Marque campos e números antigos como reserved para impedir uso acidental.

Carregando o serviço

import grpc from '@grpc/grpc-js';
import protoLoader from '@grpc/proto-loader';

const definition = protoLoader.loadSync(
  new URL('./orders.proto', import.meta.url).pathname,
  {
    keepCase: false,
    longs: String,
    enums: String,
    defaults: true,
    oneofs: true
  }
);

const proto = grpc.loadPackageDefinition(definition);
const OrdersService = proto.orders.v1.OrdersService;

Decida como representar int64. JavaScript Number não representa todos os inteiros de 64 bits com precisão. Usar string evita perda silenciosa, mas exige validação e conversão controlada.

Implementando método unary

async function getOrder(call, callback) {
  try {
    const orderId = validateOrderId(call.request.orderId);
    const order = await orderRepository.findById(orderId);

    if (!order) {
      return callback({
        code: grpc.status.NOT_FOUND,
        message: 'Order not found'
      });
    }

    callback(null, mapOrder(order));
  } catch (error) {
    call.log?.error({ err: error }, 'get_order_failed');
    callback(mapGrpcError(error));
  }
}

O handler recebe a chamada e um callback. Valide a entrada antes de consultar dependências. Não envie stack trace, SQL ou mensagens sensíveis. Use códigos de status gRPC coerentes.

Iniciando o servidor

const server = new grpc.Server({
  'grpc.max_receive_message_length': 1024 * 1024,
  'grpc.max_send_message_length': 1024 * 1024
});

server.addService(OrdersService.service, {
  getOrder,
  listOrders,
  createOrders
});

server.bindAsync(
  '0.0.0.0:50051',
  grpc.ServerCredentials.createSsl(
    rootCertificates,
    [{ private_key: privateKey, cert_chain: certificate }],
    true
  ),
  error => {
    if (error) throw error;
    server.start();
  }
);

Em produção, use TLS. O exemplo exige certificado de cliente, configurando mTLS. Isso autentica a conexão, mas a aplicação ainda precisa autorizar cada identidade e operação.

Cliente com deadline

const client = new OrdersService(
  'orders.internal.example:50051',
  grpc.credentials.createSsl(caCertificate)
);

const deadline = new Date(Date.now() + 2000);

client.getOrder(
  { orderId },
  { deadline },
  (error, order) => {
    if (error) {
      handleGrpcError(error);
      return;
    }

    console.log(order);
  }
);

Toda chamada deve ter deadline. Sem prazo, uma dependência lenta pode prender recursos indefinidamente. Propague o tempo restante quando um serviço chama outro, reduzindo o orçamento a cada etapa.

Status gRPC

  • INVALID_ARGUMENT: entrada malformada;
  • UNAUTHENTICATED: credencial ausente ou inválida;
  • PERMISSION_DENIED: identidade válida sem acesso;
  • NOT_FOUND: recurso inexistente;
  • ALREADY_EXISTS: conflito de criação;
  • FAILED_PRECONDITION: estado não permite a operação;
  • RESOURCE_EXHAUSTED: limite ou quota excedida;
  • UNAVAILABLE: dependência temporariamente indisponível;
  • DEADLINE_EXCEEDED: prazo terminou;
  • INTERNAL: falha inesperada.

Não converta todas as falhas em INTERNAL. Códigos precisos ajudam clientes a decidir entre corrigir entrada, autenticar, parar ou tentar novamente.

Metadata

Metadata transporta autenticação, correlationId, locale e outras informações pequenas. Não use para payloads grandes.

const metadata = new grpc.Metadata();
metadata.set('authorization', `Bearer ${accessToken}`);
metadata.set('x-request-id', requestId);

client.getOrder(
  { orderId },
  metadata,
  { deadline },
  callback
);

Tokens devem ser enviados apenas sobre TLS e nunca registrados. Valide tamanho e formato de metadata recebida.

Streaming do servidor

async function listOrders(call) {
  try {
    const input = validateListRequest(call.request);

    for await (const order of orderRepository.streamByCustomer(input)) {
      if (call.cancelled) break;

      const canContinue = call.write(mapOrder(order));
      if (!canContinue) {
        await once(call, 'drain');
      }
    }

    call.end();
  } catch (error) {
    call.destroy(mapGrpcError(error));
  }
}

Respeite cancelamento e backpressure. Ignorar o retorno de write() pode acumular dados em memória quando o cliente consome lentamente.

Streaming do cliente

function createOrders(call, callback) {
  let created = 0;
  let chain = Promise.resolve();

  call.on('data', request => {
    chain = chain.then(async () => {
      const input = validateCreateOrder(request);
      await createOrder(input);
      created += 1;
    });
  });

  call.on('end', async () => {
    try {
      await chain;
      callback(null, { created });
    } catch (error) {
      callback(mapGrpcError(error));
    }
  });
}

Limite quantidade, tamanho e duração do stream. Defina se o processamento é atômico ou parcial e documente como o cliente identifica itens rejeitados.

Compatibilidade de Proto

Adicione campos novos com números novos e comportamento padrão seguro. Não mude tipo ou significado de um campo existente. Remoções devem reservar nome e número.

message Order {
  reserved 6, 7;
  reserved "legacy_code";

  string id = 1;
  string customer_id = 2;
  int64 total_cents = 3;
  string status = 4;
  string created_at = 5;
}

Automatize lint e verificação de breaking changes no pipeline. Trate o proto como uma API versionada.

Retries

Tente novamente apenas operações idempotentes ou protegidas por chave de idempotência. Use backoff com jitter e respeite deadline. Não repita INVALID_ARGUMENT, PERMISSION_DENIED ou outras falhas permanentes. Veja retry com backoff no Node.js.

Interceptors e middleware

Interceptors podem centralizar logs, autenticação, métricas e tracing. Mantenha a regra de autorização próxima do recurso e não confie apenas em um check global de token. Diferentes métodos podem exigir escopos e condições distintas.

Segurança

  • use TLS e valide hostname;
  • considere mTLS para serviços internos;
  • valide tokens e metadata;
  • autorize por método e recurso;
  • limite tamanho de mensagens e streams;
  • defina deadlines;
  • não exponha reflection em redes públicas sem necessidade;
  • proteja logs e certificados.

Observabilidade

Registre serviço, método, status, duração, deadline, tamanho de mensagens e requestId. Métricas devem incluir taxa, erros, latência e chamadas canceladas. Traces distribuídos ajudam a visualizar cadeias entre serviços, mas metadata precisa ser propagada de forma controlada.

Não use IDs únicos como labels. Use-os em logs e spans. Consulte OpenTelemetry no Node.js para instrumentação.

Health checks

Implemente o protocolo padrão de health checking do gRPC quando a infraestrutura suportar. Diferencie processo vivo de serviço pronto. Uma dependência opcional não deve necessariamente tornar todo o serviço indisponível.

Graceful shutdown

process.on('SIGTERM', () => {
  server.tryShutdown(error => {
    if (error) {
      server.forceShutdown();
      process.exit(1);
    }

    process.exit(0);
  });

  setTimeout(() => {
    server.forceShutdown();
    process.exit(1);
  }, 15000).unref();
});

Durante shutdown, pare de aceitar chamadas novas e permita que operações em andamento terminem dentro de um prazo.

Testes

Suba o servidor em porta aleatória com credenciais de teste. Valide método unary, streaming, deadlines, cancelamento, metadata, autenticação e status. Não dependa de um ambiente compartilhado.

import test from 'node:test';
import assert from 'node:assert/strict';

test('retorna NOT_FOUND', async () => {
  await assert.rejects(
    () => getOrderAsync({ orderId: 'missing' }),
    error => error.code === grpc.status.NOT_FOUND
  );
});

Separe casos de uso da camada gRPC para testá-los sem rede. O guia de testes unitários com Jest ajuda nessa organização.

Erros comuns

  • Sem deadline: chamadas podem ficar presas;
  • Reutilizar números Proto: clientes antigos interpretam dados incorretamente;
  • Ignorar cancelamento: o servidor continua trabalho inútil;
  • Não controlar backpressure: streams consomem memória;
  • Retry em operação não idempotente: efeitos podem duplicar;
  • Usar INTERNAL para tudo: clientes não sabem como reagir.

Checklist

  • proto está versionado e revisado;
  • campos removidos estão reserved;
  • todas as chamadas possuem deadline;
  • TLS e autenticação estão ativos;
  • status são mapeados corretamente;
  • streams respeitam cancelamento e backpressure;
  • retries são limitados e idempotentes;
  • shutdown e health checks foram testados.

Referências oficiais

Conclusão

gRPC com Node.js oferece contratos claros, geração de clientes e streaming sobre HTTP/2. O benefício aparece principalmente em comunicação interna e ecossistemas com várias linguagens.

Para operar com segurança, trate deadlines, códigos de status, compatibilidade, TLS, cancelamento e reentregas como parte do contrato. Com observabilidade e testes de integração, o serviço se torna previsível mesmo quando a rede e as dependências falham.

Os 10 Melhores Cursos de Programação de 2026

Descubra os melhores cursos de programação. Aprenda a escolher o curso ideal para iniciar ou avançar na carreira de desenvolvedor

POSTS RELACIONADOS

Ver todos

Seta para a direita