Protobuf no Node.js usa contratos .proto para serializar mensagens binárias compactas e gerar código entre linguagens. O formato é amplamente usado em gRPC, eventos e comunicação entre serviços porque mantém números de campo estáveis e oferece regras claras para evolução.
O schema é o centro do contrato. Nomes podem mudar internamente, mas os números de campo não devem ser reutilizados. Uma alteração aparentemente pequena pode tornar mensagens antigas ambíguas se o time não respeitar compatibilidade.
Primeiro arquivo .proto
syntax = "proto3";
package orders.v1;
message OrderCreated {
string event_id = 1;
string order_id = 2;
int64 amount_cents = 3;
string currency = 4;
}Cada campo possui um número. Ele faz parte do formato binário e precisa permanecer estável.
Carregamento dinâmico
import protobuf from 'protobufjs';
const root = await protobuf.load('orders.proto');
const OrderCreated = root.lookupType('orders.v1.OrderCreated');Em projetos grandes, geração estática pode reduzir custo de startup e melhorar tipagem.
Validando e codificando
const payload = {
eventId: crypto.randomUUID(),
orderId: 'ord-123',
amountCents: 12990,
currency: 'BRL'
};
const error = OrderCreated.verify(payload);
if (error) throw new Error(error);
const message = OrderCreated.create(payload);
const buffer = OrderCreated.encode(message).finish();Decodificando
const decoded = OrderCreated.decode(buffer);
const object = OrderCreated.toObject(decoded, {
longs: String,
defaults: true
});Configurar longs: String evita perda de precisão em inteiros de 64 bits. Escolha uma convenção única para toda a aplicação.
int64 e JavaScript
O tipo Number representa inteiros com segurança apenas até Number.MAX_SAFE_INTEGER. IDs, timestamps em nanossegundos e contadores podem exceder esse limite.
Opções comuns:
- converter para string;
- usar BigInt com biblioteca compatível;
- usar um tipo Long;
- modelar como string no contrato quando não há operações aritméticas.
Valores padrão no proto3
Campos escalares ausentes assumem valores padrão, como string vazia, zero e false. Isso pode dificultar distinguir “não enviado” de “enviado como zero”.
optional string coupon_code = 5;Use optional, wrappers ou mensagens aninhadas quando presença for importante.
Enums
enum OrderStatus {
ORDER_STATUS_UNSPECIFIED = 0;
ORDER_STATUS_PENDING = 1;
ORDER_STATUS_PAID = 2;
}Reserve o valor zero para desconhecido. Consumidores precisam tolerar valores novos e não devem falhar ao receber enum que ainda não conhecem.
Mensagens aninhadas
message Money {
int64 amount_cents = 1;
string currency = 2;
}
message OrderCreated {
string event_id = 1;
string order_id = 2;
Money amount = 3;
}Mensagens reutilizáveis tornam o contrato mais expressivo, mas acoplamento excessivo pode fazer uma mudança afetar muitos serviços.
oneof
message PaymentMethod {
oneof method {
Card card = 1;
Pix pix = 2;
}
}oneof garante que somente uma alternativa está ativa. Valide também regras de negócio depois da decodificação.
Evolução compatível
Práticas seguras:
- adicione novos campos com números novos;
- não altere o significado de campo existente;
- não reutilize números removidos;
- reserve números e nomes antigos;
- tolere campos desconhecidos;
- não torne interpretação mais restritiva sem versão.
Reservando campos
message Customer {
reserved 3, 7 to 9;
reserved "legacy_code";
string id = 1;
string name = 2;
}Isso impede reutilização acidental durante compilação.
Mudanças de tipo
Alguns tipos possuem representação wire compatível, mas isso não significa que a semântica continue correta. Evite trocar tipo em campo existente. Crie um campo novo e migre consumidores.
Versionamento de packages
package orders.v2;Quando a mudança é realmente incompatível, use novo package, serviço ou mensagem. Consulte API Versioning no Node.js.
gRPC
service OrderService {
rpc GetOrder(GetOrderRequest) returns (Order);
rpc StreamOrders(StreamOrdersRequest) returns (stream Order);
}gRPC combina Protobuf com HTTP/2, deadlines, metadata e streaming. O contrato precisa documentar erros, idempotência e limites além das mensagens.
Eventos
Ao publicar em Kafka ou RabbitMQ, inclua metadados em headers ou envelope:
- event ID;
- tipo e versão;
- timestamp;
- tenant;
- correlation ID;
- schema version.
Veja Kafka no Node.js.
Determinismo
A codificação não deve ser usada como hash canônico sem confirmar modo determinístico e regras da implementação. Mapas e campos desconhecidos podem alterar ordem. Para assinatura, defina uma representação canônica ou assine bytes exatamente transmitidos.
Tamanho e limites
Mensagens binárias também podem causar abuso. Limite bytes, profundidade, quantidade de elementos e comprimento de strings antes ou durante processamento.
Campos desconhecidos
Uma vantagem do formato é que leitores antigos podem ignorar campos novos. Verifique como a biblioteca preserva ou descarta esses campos ao decodificar e recodificar; isso importa em proxies que repassam mensagens.
Geração de TypeScript
Ferramentas podem gerar tipos e clientes. Fixe versões do compilador e plugins no CI para builds reproduzíveis. Revise código gerado como artefato de contrato.
Schema Registry e Buf
Registries e ferramentas de lint podem detectar breaking changes. Mesmo sem infraestrutura dedicada, o CI deve comparar o contrato novo com o publicado.
Consulte Testes de Contrato no Node.js.
Validação de negócio
verify() confirma tipos estruturais. Depois, valide moeda, limites, permissões, estados e relacionamentos.
if (BigInt(object.amountCents) <= 0n) {
throw new Error('Valor deve ser positivo');
}Observabilidade
Monitore falhas de decode, tamanho de mensagens, versão do package, latência de serialização e payloads enviados à DLQ. Não registre conteúdo sensível em texto.
Testes
- round trip;
- campos desconhecidos;
- novo campo com leitor antigo;
- campo removido e reservado;
- int64 acima do limite;
- enum desconhecido;
- optional ausente e vazio;
- oneof inválido;
- payload truncado;
- mensagem acima do limite.
Erros comuns
- reutilizar número de campo;
- mudar significado sem versão;
- converter int64 para Number;
- confundir valor padrão com presença;
- não tolerar enum novo;
- não limitar tamanho;
- gerar código com versões diferentes;
- acreditar que schema valida negócio;
- assinar objeto reserializado.
Checklist
- números estáveis;
- campos removidos reservados;
- longs tratados;
- presença modelada;
- enums tolerantes;
- limites de mensagem;
- geração reproduzível;
- CI detecta breaking changes;
- validação de negócio;
- testes entre versões.
Conclusão
Protobuf no Node.js fornece contratos compactos e interoperáveis para APIs e eventos. O formato permite evolução quando números de campo, presença e compatibilidade são tratados como parte da arquitetura.
Não reutilize campos, cuide de int64 e valide regras depois do decode. Com geração fixa, testes de contrato e limites de payload, Protobuf permite integrar serviços em várias linguagens com mudanças previsíveis.
Consulte a documentação oficial do Protocol Buffers.



