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:testO 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.




