avro-js vs avsc
Apache Avro Serialization Libraries for JavaScript
avro-jsavsc

Apache Avro Serialization Libraries for JavaScript

avro-js and avsc are both JavaScript implementations of the Apache Avro specification, designed for serializing and deserializing structured data using binary encoding. avsc is a comprehensive, pure-JavaScript library that supports the full Avro spec, including schema resolution, logical types, and fingerprinting, making it suitable for both Node.js and browser environments. avro-js is an older implementation that provides core encoding and decoding capabilities but has seen less active development and feature parity compared to avsc in recent years.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
avro-js03,260170 kB1716 months agoApache-2.0
avsc01,379264 kB269 months agoMIT

Apache Avro in JavaScript: avsc vs avro-js

When working with Apache Avro in the JavaScript ecosystem, you need a library that can handle schema definition, binary encoding, and decoding reliably. avsc and avro-js are the two primary options, but they differ significantly in maintenance, feature completeness, and API design. Let's break down how they handle real-world serialization tasks.

📜 Schema Handling and Type Creation

avsc provides a robust API for parsing schemas and creating types. It supports schema resolution and evolution out of the box.

// avsc: Parse schema and create type
const avsc = require('avsc');

const schema = {
  type: 'record',
  name: 'User',
  fields: [
    { name: 'id', type: 'long' },
    { name: 'username', type: 'string' }
  ]
};

const type = avsc.Type.forSchema(schema);
const value = { id: 123, username: 'alice' };
const buffer = type.toBuffer(value);

avro-js uses a similar concept but with a different API structure. It requires creating a schema object first.

// avro-js: Create schema and encode
const avro = require('avro-js');

const schema = avro.schema({
  type: 'record',
  name: 'User',
  fields: [
    { name: 'id', type: 'long' },
    { name: 'username', type: 'string' }
  ]
});

const value = { id: 123, username: 'alice' };
const buffer = schema.toBuffer(value);

🔄 Encoding and Decoding Data

avsc handles encoding and decoding with built-in validation. It throws clear errors if the data doesn't match the schema.

// avsc: Decode with validation
const decoded = type.fromBuffer(buffer);
// decoded: { id: 123, username: 'alice' }

// Invalid data throws an error
try {
  type.fromBuffer(Buffer.from('invalid'));
} catch (err) {
  console.error(err.message); // Clear error message
}

avro-js also supports encoding and decoding but may have less detailed error messages in older versions.

// avro-js: Decode data
const decoded = schema.fromBuffer(buffer);
// decoded: { id: 123, username: 'alice' }

// Error handling varies by version
try {
  schema.fromBuffer(Buffer.from('invalid'));
} catch (err) {
  console.error(err.message);
}

🧬 Advanced Features: Logical Types and Unions

avsc has first-class support for logical types (e.g., dates, decimals) and complex unions.

// avsc: Logical types support
const schemaWithLogical = {
  type: 'record',
  name: 'Event',
  fields: [
    { name: 'timestamp', type: { type: 'long', logicalType: 'timestamp-millis' } }
  ]
};

const type = avsc.Type.forSchema(schemaWithLogical);
const buffer = type.toBuffer({ timestamp: new Date() });
const event = type.fromBuffer(buffer); // timestamp is a Date object

avro-js supports logical types but may require additional configuration or have limited support for newer logical types depending on the version.

// avro-js: Logical types support
const schemaWithLogical = avro.schema({
  type: 'record',
  name: 'Event',
  fields: [
    { name: 'timestamp', type: { type: 'long', logicalType: 'timestamp-millis' } }
  ]
});

const buffer = schemaWithLogical.toBuffer({ timestamp: new Date() });
const event = schemaWithLogical.fromBuffer(buffer);

🛡️ Schema Evolution and Compatibility

avsc excels at schema evolution. It allows you to decode data written with an older schema using a new schema (reader/writer schema resolution).

// avsc: Schema resolution
const writerType = avsc.Type.forSchema(writerSchema);
const readerType = avsc.Type.forSchema(readerSchema);

const buffer = writerType.toBuffer({ id: 1, username: 'alice', email: 'a@example.com' });

// Decode with reader schema (missing fields handled gracefully)
const value = readerType.fromBuffer(buffer, writerType);

avro-js supports schema evolution but the API for reader/writer resolution is less documented and may require manual handling in some cases.

// avro-js: Schema resolution
// Similar concept but API may differ slightly
const buffer = writerSchema.toBuffer(data);
const value = readerSchema.fromBuffer(buffer, writerSchema);

🌐 Browser and Bundle Considerations

avsc is designed to work in both Node.js and browser environments. It has no native dependencies.

// avsc: Browser usage
// Works with bundlers like Webpack or Vite
import * as avsc from 'avsc';
// No special configuration needed for most cases

avro-js also works in browsers but may require polyfills for older environments or specific bundler configurations.

// avro-js: Browser usage
import * as avro from 'avro-js';
// May need additional setup for certain bundlers

📝 TypeScript Support

avsc includes TypeScript definitions, making it easier to use in typed projects.

// avsc: TypeScript usage
import * as avsc from 'avsc';

const schema: avsc.Schema = {
  type: 'record',
  name: 'User',
  fields: [{ name: 'id', type: 'long' }]
};

const type = avsc.Type.forSchema(schema);

avro-js has community-maintained types or older definitions that may not cover all APIs.

// avro-js: TypeScript usage
import * as avro from 'avro-js';
// Types may be less complete than avsc

📊 Summary Table

Featureavscavro-js
Maintenance✅ Active⚠️ Slower updates
Schema Resolution✅ Full support✅ Supported
Logical Types✅ Comprehensive✅ Basic support
TypeScript✅ Built-in types⚠️ Community types
Browser Ready✅ Yes✅ Yes
API Clarity✅ Modern⚠️ Legacy feel

💡 The Big Picture

avsc is the clear choice for modern JavaScript development. It offers better maintenance, clearer APIs, and more robust support for advanced Avro features like schema evolution and logical types. Its TypeScript support makes it ideal for large-scale frontend or full-stack applications.

avro-js is a viable option only for legacy projects where migration is too costly. For any new work, the ecosystem has standardized around avsc due to its reliability and feature completeness.

Final Thought: Data serialization is critical for interoperability. Choosing a well-maintained library like avsc reduces the risk of bugs and ensures compatibility with the broader Apache Avro ecosystem.

How to Choose: avro-js vs avsc

  • avro-js:

    Choose avro-js only if you are maintaining a legacy system that already depends on it and migration costs are prohibitive. For new development, it is generally not recommended due to slower update cycles and fewer modern features compared to avsc.

  • avsc:

    Choose avsc for new projects requiring full Avro specification support, active maintenance, and robust TypeScript definitions. It is the industry standard for JavaScript Avro implementations, offering advanced features like schema evolution, logical types, and fingerprinting that are critical for production data pipelines.

README for avro-js

Avro-js

Pure JavaScript implementation of the Avro specification.

Features

  • Fast! Typically twice as fast as JSON with much smaller encodings.
  • Full Avro support, including recursive schemas, sort order, and evolution.
  • Serialization of arbitrary JavaScript objects via logical types.
  • Unopinionated 64-bit integer compatibility.
  • No dependencies, avro-js even runs in the browser.

Installation

$ npm install avro-js

avro-js is compatible with all versions of node.js since 0.11 and major browsers via browserify.

Documentation

See doc/ folder.

Examples

Inside a node.js module, or using browserify:

var avro = require('avro-js');
  • Encode and decode objects:

    // We can declare a schema inline:
    var type = avro.parse({
      name: 'Pet',
      type: 'record',
      fields: [
        {name: 'kind', type: {name: 'Kind', type: 'enum', symbols: ['CAT', 'DOG']}},
        {name: 'name', type: 'string'}
      ]
    });
    var pet = {kind: 'CAT', name: 'Albert'};
    var buf = type.toBuffer(pet); // Serialized object.
    var obj = type.fromBuffer(buf); // {kind: 'CAT', name: 'Albert'}
    
  • Generate random instances of a schema:

    // We can also parse a JSON-stringified schema:
    var type = avro.parse('{"type": "fixed", "name": "Id", "size": 4}');
    var id = type.random(); // E.g. Buffer([48, 152, 2, 123])
    
  • Check whether an object fits a given schema:

    // Or we can specify a path to a schema file (not in the browser):
    var type = avro.parse('./Person.avsc');
    var person = {name: 'Bob', address: {city: 'Cambridge', zip: '02139'}};
    var status = type.isValid(person); // Boolean status.
    
  • Get a readable stream of decoded records from an Avro container file (not in the browser):

    avro.createFileDecoder('./records.avro')
      .on('metadata', function (type) { /* `type` is the writer's type. */ })
      .on('data', function (record) { /* Do something with the record. */ });
    
  • Implement recursive schemata (due to lack of duck-typing):

      // example type: linked list with one long-int as element value
      const recursiveRecordType =  avro.parse({
        "type": "record",
        "name": "LongList",
        "fields" : [
          {"name": "value", "type": "long"},             
          {"name": "next", "type": ["null", "LongList"]} // optional next element via recursion
        ]
      });
    
      // will work
      const validRecursiveRecordDTO = {
        value: 1,
        next: {
          // no duck-typing support: from first nested level on the 
          // recursive type has to be explicitly specified.
          LongList: {
            value: 2,
            next: null
          }
        }
      };
      const serializedValid = recursiveRecordType.parse(validRecursiveRecordDTO);
      
    
      // will throw error
      const invalidRecursiveRecordDTO = {
        value: 1,
        next: {
            value: 2,
            next: null
        }
      };
      const serializedInvalid = recursiveRecordType.parse(invalidRecursiveRecordDTO);