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

Avro no Node.js

Atualizado em: 14 de setembro de 2026

Rack de servidores processando fluxos de dados no Node.js

O Avro no Node.js é uma opção de serialização binária orientada por schema, muito usada em pipelines de dados e eventos Kafka. Diferente de JSON, o formato não repete nomes de campos em cada mensagem. O produtor e o consumidor compartilham um contrato, reduzindo tamanho e permitindo evolução controlada.

Avro é especialmente útil quando várias aplicações precisam trocar eventos por anos, com produtores e consumidores atualizados em momentos diferentes. O formato define regras para records, unions, arrays, enums, defaults e compatibilidade entre writer schema e reader schema.

Neste guia, você aprenderá a criar schemas Avro, validar e codificar dados no Node.js, trabalhar com tipos long, unions e enums, integrar com Kafka e Schema Registry, testar evolução e evitar mudanças incompatíveis.

O que é Apache Avro?

Apache Avro é um sistema de serialização de dados que usa schemas JSON para descrever estruturas binárias. A especificação oficial do Avro define tipos primitivos, tipos complexos, resolução de schemas e formatos de arquivo e RPC.

Um record simples:

{
  "type": "record",
  "name": "OrderCreated",
  "namespace": "com.codigofacil.orders",
  "fields": [
    { "name": "orderId", "type": "string" },
    { "name": "totalCents", "type": "long" },
    { "name": "occurredAt", "type": "string" }
  ]
}

Por que Avro é compacto?

O schema informa a ordem e o tipo dos campos. A mensagem binária contém apenas os valores codificados. Em JSON, cada evento repete orderId, totalCents e outros nomes.

O ganho depende do payload, mas é relevante em tópicos de alto volume. O custo é precisar gerenciar schemas e ferramentas de inspeção.

Biblioteca avsc

Uma biblioteca popular para Node.js é avsc:

npm install avsc
import avro from 'avsc';

const type = avro.Type.forSchema({
  type: 'record',
  name: 'OrderCreated',
  fields: [
    { name: 'orderId', type: 'string' },
    { name: 'totalCents', type: 'long' },
    { name: 'occurredAt', type: 'string' }
  ]
});

Validando dados

const event = {
  orderId: 'order-42',
  totalCents: 15990,
  occurredAt: new Date().toISOString()
};

if (!type.isValid(event)) {
  throw new Error('Evento incompatível com schema Avro');
}

Valide antes de publicar para falhar perto da origem do erro.

Codificando

const buffer = type.toBuffer(event);

O resultado é um Buffer que pode ser enviado ao Kafka, salvo em arquivo ou transmitido por outro protocolo.

Decodificando

const decoded = type.fromBuffer(buffer);

console.log(decoded.orderId);

Quando o writer e reader usam schemas diferentes, a resolução precisa conhecer os dois contratos.

Tipos primitivos

Avro possui:

  • null;
  • boolean;
  • int;
  • long;
  • float;
  • double;
  • bytes;
  • string.

Escolha o tipo pelo domínio. Valores monetários devem usar inteiro em centavos ou decimal lógico, não float.

O problema de long no JavaScript

JavaScript usa números IEEE-754 e não representa com segurança todos os inteiros de 64 bits. Se um long pode ultrapassar Number.MAX_SAFE_INTEGER, configure a biblioteca para BigInt ou representação customizada.

const value = BigInt('9223372036854775807');

Teste serialização e JSON, porque JSON.stringify não aceita BigInt por padrão.

Campos opcionais

Avro usa union:

{
  "name": "couponCode",
  "type": ["null", "string"],
  "default": null
}

A ordem importa: o default precisa corresponder ao primeiro tipo da union segundo as regras aplicáveis.

Defaults e evolução

Adicionar um campo com default permite que leitores novos processem mensagens antigas:

{
  "name": "currency",
  "type": "string",
  "default": "BRL"
}

O default não significa que o produtor omite o campo durante codificação em todos os casos; ele participa principalmente da resolução de schema.

Enums

{
  "name": "status",
  "type": {
    "type": "enum",
    "name": "OrderStatus",
    "symbols": ["CREATED", "PAID", "CANCELLED"]
  }
}

Adicionar símbolos pode quebrar consumidores antigos que não conhecem o novo valor. Use default de enum quando suportado e teste compatibilidade.

Arrays

{
  "name": "items",
  "type": {
    "type": "array",
    "items": {
      "type": "record",
      "name": "OrderItem",
      "fields": [
        { "name": "productId", "type": "string" },
        { "name": "quantity", "type": "int" }
      ]
    }
  }
}

Maps

{
  "name": "metadata",
  "type": {
    "type": "map",
    "values": "string"
  },
  "default": {}
}

Maps sempre possuem chaves string. Não use metadata como depósito sem governança.

Logical types

Avro define tipos lógicos sobre tipos primitivos, como:

  • date;
  • time-millis;
  • timestamp-millis;
  • uuid;
  • decimal.

Confirme suporte da biblioteca Node.js. Uma ferramenta pode tratar o valor apenas como número ou bytes sem conversão automática.

Datas

Para interoperabilidade simples, muitos eventos usam timestamp ISO em string:

{
  "name": "occurredAt",
  "type": "string"
}

Para eficiência, timestamp-millis usa long. Documente timezone UTC e precisão.

Decimal

Valores decimais precisos podem usar logical type decimal sobre bytes ou fixed:

{
  "name": "amount",
  "type": {
    "type": "bytes",
    "logicalType": "decimal",
    "precision": 12,
    "scale": 2
  }
}

Verifique suporte da biblioteca e nunca converta por float sem cuidado.

Named types

Records, enums e fixed possuem nome e namespace. Isso evita repetir schemas e permite referências:

{
  "type": "record",
  "name": "Customer",
  "namespace": "com.codigofacil.customers",
  "fields": []
}

Mudar o nome completo pode ser breaking.

Aliases

Aliases ajudam a resolver renomeações em casos suportados:

{
  "name": "customerId",
  "aliases": ["clientId"],
  "type": "string"
}

Não dependa de aliases sem testar todos os consumidores.

Writer e reader schema

O writer schema descreve como a mensagem foi gravada. O reader schema descreve como o consumidor quer lê-la. Avro resolve diferenças compatíveis:

  • campos extras do writer podem ser ignorados;
  • campos ausentes no writer podem usar default do reader;
  • alguns tipos numéricos podem ser promovidos;
  • nomes e aliases precisam corresponder.

Schema Registry

No Kafka, o wire format costuma incluir um schema ID. O consumidor busca o writer schema no registry. Veja Schema Registry no Node.js.

KafkaJS

Depois de codificar:

await producer.send({
  topic: 'orders.created.v1',
  messages: [{
    key: event.orderId,
    value: buffer
  }]
});

Consulte Kafka com Node.js.

Envelope de evento

Um schema consistente pode incluir:

{
  "eventId": "evt-123",
  "eventType": "order.created",
  "eventVersion": 1,
  "occurredAt": "2026-09-14T18:00:00Z",
  "correlationId": "corr-42",
  "data": {}
}

O envelope ajuda observabilidade e idempotência. Veja Idempotência em APIs Node.js.

Compatibilidade backward

Um reader novo deve ler dados antigos. Adicionar campo com default costuma ser compatível. Remover campo do reader pode ser compatível se ele simplesmente ignora dados antigos.

Compatibilidade forward

Um reader antigo deve ler dados novos. Adicionar campo ao writer tende a funcionar porque o reader ignora o campo desconhecido.

Mudanças incompatíveis

  • renomear sem alias;
  • trocar string por int;
  • remover default necessário;
  • mudar record name;
  • alterar semântica mantendo o mesmo campo;
  • reutilizar enum com significado diferente.

Teste com fixtures

Salve bytes de versões anteriores:

test('schema atual lê evento v1', () => {
  const buffer = readFileSync('fixtures/order-created-v1.avro');
  const event = currentType.fromBuffer(buffer);

  assert.equal(event.currency, 'BRL');
});

Fixtures protegem compatibilidade real.

Teste de round trip

const encoded = type.toBuffer(event);
const decoded = type.fromBuffer(encoded);
assert.deepEqual(decoded, event);

Round trip não substitui testes entre versões diferentes.

Geração de tipos TypeScript

É possível gerar interfaces a partir de schemas ou gerar schemas a partir de tipos, mas evite duas fontes de verdade. Escolha o contrato principal e valide o código gerado no CI.

TypeScript desaparece em runtime; Avro continua validando dados reais.

Performance

Compile schemas uma vez no startup:

const orderCreatedType = avro.Type.forSchema(schema);

Não reconstrua o tipo para cada mensagem. Meça encoding, decoding, tamanho e garbage collection.

Segurança

  • Limite tamanho de payload.
  • Não aceite schemas arbitrários de usuários.
  • Restrinja registro no registry.
  • Valide logical types.
  • Proteja dados sensíveis.
  • Monitore falhas de decode.

Observabilidade

Registre schema ID, event type e versão, mas não o payload inteiro. Meça:

  • falhas de encode;
  • falhas de decode;
  • schema desconhecido;
  • latência;
  • tamanho de mensagem;
  • incompatibilidade no pipeline.

Consulte Métricas Prometheus no Node.js.

Avro versus JSON

Avro é mais compacto e possui evolução formal. JSON é mais fácil de inspecionar e integrar. Para APIs HTTP humanas, JSON costuma ser melhor. Para eventos de alto volume, Avro pode reduzir custo e erro.

Avro versus Protobuf

Avro é muito alinhado a Kafka e resolução entre writer e reader. Protobuf usa arquivos .proto, field numbers e geração de código. O próximo artigo detalha Protobuf.

Erros comuns

  • long como Number: perde precisão.
  • Campo opcional sem union: validação falha.
  • Default incorreto: evolução quebra.
  • Schema compilado por mensagem: performance cai.
  • Sem fixtures antigas: compatibilidade não é testada.
  • Alterar sem registry: consumidores quebram.
  • BigInt serializado em JSON: gera erro.
  • Schema sem ownership: contratos se degradam.

Conclusão

O Avro no Node.js fornece serialização compacta e evolução de schemas para eventos duráveis. Records, unions, defaults e resolução entre writer e reader permitem atualizar produtores e consumidores em momentos diferentes.

Compile schemas uma vez, trate long e decimal corretamente, registre contratos e teste bytes antigos. Com Schema Registry e Kafka, Avro cria uma base sólida para mensagens pequenas, validadas e compatíveis ao longo do tempo.

10 melhores cursos de programação em 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