gRPC no Node.js permite criar APIs de alto desempenho baseadas em contratos Protobuf e HTTP/2. Em vez de definir endpoints e JSON manualmente, cliente e servidor compartilham um arquivo .proto que descreve serviços, métodos e mensagens.
O modelo é útil para comunicação interna entre serviços, sistemas poliglotas, chamadas com tipos bem definidos e streaming bidirecional. Ele não elimina a necessidade de autenticação, deadlines, observabilidade e compatibilidade de contratos.
Pacotes principais
npm install @grpc/grpc-js @grpc/proto-loader@grpc/grpc-js é a implementação em JavaScript puro. @grpc/proto-loader carrega arquivos Protobuf em tempo de execução.
Definindo o contrato
syntax = "proto3";
package catalog.v1;
service CatalogService {
rpc GetProduct(GetProductRequest) returns (Product);
}
message GetProductRequest {
string id = 1;
}
message Product {
string id = 1;
string name = 2;
int64 price_cents = 3;
}Não reutilize números de campos removidos. Preserve compatibilidade conforme o contrato evolui. Consulte Protobuf no Node.js.
Carregando o proto
import grpc from '@grpc/grpc-js';
import protoLoader from '@grpc/proto-loader';
const definition = protoLoader.loadSync('catalog.proto', {
keepCase: false,
longs: String,
enums: String,
defaults: true,
oneofs: true
});
const descriptor = grpc.loadPackageDefinition(definition);
const catalog = descriptor.catalog.v1;Decida explicitamente como representar int64. JavaScript não representa todos os inteiros de 64 bits com precisão em number.
Implementando o servidor
const server = new grpc.Server();
server.addService(catalog.CatalogService.service, {
async getProduct(call, callback) {
try {
const product = await repository.findById(call.request.id);
if (!product) {
return callback({
code: grpc.status.NOT_FOUND,
message: 'Produto não encontrado'
});
}
callback(null, {
id: product.id,
name: product.name,
priceCents: String(product.priceCents)
});
} catch (error) {
callback({
code: grpc.status.INTERNAL,
message: 'Erro interno'
});
}
}
});Não devolva stack traces ou mensagens de banco ao cliente. Traduza erros de domínio para status gRPC estáveis.
Iniciando com TLS
const credentials = grpc.ServerCredentials.createSsl(
rootCertificates,
[{ private_key: privateKey, cert_chain: certificateChain }],
false
);
server.bindAsync('0.0.0.0:50051', credentials, error => {
if (error) throw error;
server.start();
});Em ambientes internos, TLS continua importante. Para autenticação mútua, exija certificado do cliente e valide a identidade.
Criando o cliente
const client = new catalog.CatalogService(
'catalog.internal:50051',
grpc.credentials.createSsl(rootCa)
);
client.getProduct(
{ id: 'p-10' },
{ deadline: Date.now() + 1000 },
(error, response) => {
if (error) {
console.error(error.code, error.details);
return;
}
console.log(response);
}
);Toda chamada deve ter deadline. Sem limite, conexões lentas podem consumir recursos indefinidamente.
Status codes
Use códigos coerentes:
INVALID_ARGUMENT: entrada inválida;NOT_FOUND: recurso ausente;ALREADY_EXISTS: conflito de criação;FAILED_PRECONDITION: estado incompatível;UNAUTHENTICATED: credencial ausente ou inválida;PERMISSION_DENIED: identidade sem acesso;RESOURCE_EXHAUSTED: quota ou capacidade;UNAVAILABLE: falha temporária;INTERNAL: erro inesperado.
Metadata
const metadata = new grpc.Metadata();
metadata.set('authorization', `Bearer ${token}`);
metadata.set('x-request-id', requestId);
client.getProduct(request, metadata, options, callback);Metadata é semelhante a headers. Valide tamanho e allowlist. Não registre tokens.
Autenticação
O servidor pode ler metadata:
function authenticate(call) {
const values = call.metadata.get('authorization');
const value = values[0];
if (typeof value !== 'string') {
throw createGrpcError(grpc.status.UNAUTHENTICATED, 'Token ausente');
}
return verifyToken(value);
}Autenticação deve ocorrer antes da lógica de negócio. Autorização ainda precisa considerar recurso, tenant e ação.
Validação
Protobuf garante estrutura binária, não regras de negócio. Valide tamanho, formato e limites:
if (!call.request.id || call.request.id.length > 100) {
return callback({
code: grpc.status.INVALID_ARGUMENT,
message: 'id inválido'
});
}Unary versus streaming
Uma chamada unary possui uma requisição e uma resposta. gRPC também oferece server streaming, client streaming e bidirectional streaming. Escolha streaming apenas quando o fluxo incremental traz benefício real.
Compatibilidade de schema
Adicionar campos opcionais geralmente é compatível. Remover ou alterar significado exige cuidado. Marque números removidos como reserved:
message Product {
reserved 4;
reserved "legacy_code";
string id = 1;
}Geração de código
Projetos maiores podem gerar clientes e tipos durante o build, reduzindo parsing dinâmico e melhorando integração com TypeScript. Fixe versões do compilador e plugins no CI.
Load balancing
O cliente gRPC mantém conexões HTTP/2 longas. Balanceadores L4, DNS e service mesh precisam ser configurados considerando multiplexação. Um balanceador HTTP tradicional pode concentrar tráfego em poucas conexões.
Retries
Repita apenas status transitórios e operações idempotentes. Um método de criação pode concluir no servidor e perder a resposta. Use idempotency key ou identificador fornecido pelo cliente.
Consulte Retry com Backoff no Node.js.
Keepalive
Keepalive detecta conexões quebradas, mas configurações agressivas podem sobrecarregar servidores e proxies. Ajuste cliente e servidor de forma compatível.
Tamanho de mensagens
Defina limites de envio e recebimento. gRPC não deve transportar arquivos enormes sem necessidade. Para objetos grandes, use object storage e envie referência autorizada.
Observabilidade
Registre:
- serviço e método;
- status code;
- duração;
- deadline restante;
- bytes enviados e recebidos;
- retries;
- identidade técnica;
- trace ID.
Não use IDs de usuário como labels de métricas. Integre com OpenTelemetry e propagação de contexto.
Health checking
Implemente o protocolo padrão de health checking para que orquestradores e balanceadores consultem disponibilidade. O processo estar vivo não significa que o serviço está pronto.
Shutdown gracioso
process.once('SIGTERM', () => {
server.tryShutdown(error => {
if (error) server.forceShutdown();
});
});Pare de aceitar novas chamadas, aguarde as ativas dentro de um prazo e force o encerramento somente ao final.
Testes
- contrato carregado;
- resposta de sucesso;
- entrada inválida;
- não encontrado;
- autenticação e autorização;
- deadline excedida;
- mensagem acima do limite;
- indisponibilidade transitória;
- compatibilidade entre versões;
- shutdown com chamada ativa.
Erros comuns
- chamada sem deadline;
- usar
numberpara int64 sem avaliar precisão; - vazar erro interno;
- reutilizar field numbers;
- retries em métodos não idempotentes;
- mensagens enormes;
- conexão sem TLS;
- confundir autenticação com autorização;
- não observar status codes.
Conclusão
gRPC no Node.js oferece contratos tipados, HTTP/2 e diferentes modelos de streaming para comunicação entre serviços. O ganho de desempenho e consistência depende de um desenho cuidadoso de schemas e operações.
Use deadlines, TLS, validação, status codes estáveis e compatibilidade Protobuf. Trate observabilidade, retries e shutdown como partes do protocolo, não como detalhes posteriores.
Consulte a documentação oficial de gRPC para Node.js e o repositório grpc-node.



