Library / SDK
apache/cassandra-nodejs-driver avatar
apache/cassandra-nodejs-driver

The Cassandra Node.js driver streams rows as they arrive

Node.js Driver for Apache Cassandra

1,269 stars207 forksJavaScriptApache-2.0

At a glance

What is it?
cassandra-driver talks to Cassandra, DSE, HCD and Astra DB over the binary protocol and CQL only, with two runtime dependencies. The features that matter for a large table are `eachRow` and `stream`, which parse rows as they arrive, and a default fetch size of 5000 that stops one response from returning everything.
Who is it for?
Adopt cassandra-driver if your data is in Cassandra and you want the binary protocol, prepared statements and streaming rather than a query layer over HTTP. Skip it if you want a schema or migrations,.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 8 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Binary protocol only, and no ORM in sight

The driver describes itself as a modern, feature-rich and highly tunable Node.js client library for Apache Cassandra, DSE, HCD and Astra DB, using exclusively Cassandra's binary protocol and Cassandra Query Language.

The word exclusively does real work in that sentence. There is no REST gateway, no Thrift, no CQL over HTTP fallback. Everything goes through the native protocol, which is why the driver needs a data centre name and contact points at construction time rather than a URL.

The feature list is a driver's feature list, not a framework's: simple, prepared and batch statements; asynchronous IO with parallel execution and request pipelining; connection pooling; automatic node discovery; automatic reconnection; configurable load balancing and retry policies; and a note that it works with any cluster size.

Two entries are about developer ergonomics rather than performance. There is a built-in object mapper, and there is a built-in TypeScript support entry with an `index.d.ts` at the package root and `types` pointing at it.

The runtime dependency list is two packages, `adm-zip` and `long`, which is unusually small for a database client of this scope. `long` is the 64-bit integer support that a database with arbitrary precision values needs, and `adm-zip` is there for reading the secure connect bundle used by hosted deployments.

Everything else in the manifest is development only: mocha, chai, sinon, proxyquire, kerberos, and one Node types package. Tests run on mocha with a five second timeout, split into unit, integration short and integration long.

Two API styles and one construction

The basic usage example is a client, a query with a marker, and a promise.

javascript
const cassandra = require('cassandra-driver');

const client = new cassandra.Client({
  contactPoints: ['h1', 'h2'],
  localDataCenter: 'datacenter1',
  keyspace: 'ks1'
});

const query = 'SELECT name, email FROM users WHERE key = ?';

client.execute(query, [ 'someone' ])
  .then(result => console.log('User with email %s', result.rows[0].email));

Three constructor fields carry the topology. `contactPoints` is where to start, `localDataCenter` tells the driver which data centre it is in, which is what load balancing policy needs in order to prefer nearby replicas, and `keyspace` sets a default.

The driver supports both promises and callbacks for the asynchronous methods, and the README says you can choose the approach that suits your needs. It also explains why its own examples use promises with `await`: the documentation prioritises concise examples.

That dual API is worth understanding for a deprecation reason as much as a convenience one. A driver that ships both keeps existing code working while new code picks one, but it means every method has two signatures and the documentation examples only show you one.

A separate configuration exists for the hosted service, where the client is constructed with a cloud object holding a `secureConnectBundle` path and a credentials object with a username and password. That configuration is specifically for Astra DB and it is the reason `adm-zip` is a runtime dependency, since the bundle is a zip archive containing the connection metadata.

Prepared statements carry the type mapping

The section on preparing queries makes two claims, and the second is the interesting one.

The first is performance framing: prepared statements are parsed and prepared on the Cassandra nodes and are ready for future execution, and the driver prepares the query once on each host and executes it with bound parameters.

The second is correctness framing. When preparing, the driver retrieves information about the parameter types, which the README says allows an accurate mapping between a JavaScript type and a Cassandra type.

That is the argument for prepared statements that matters more than throughput. Cassandra's CQL has types that do not map onto JavaScript's by accident: timestamps as `Date` objects, decimal and varint values wider than a double, UUIDs, collections. Without a prepared statement the driver infers from the value you pass; with one, it knows from the cluster what the column actually is, so a value that would silently lose precision is rejected or handled correctly.

The API reflects that preference. Query markers with `?` and a parameters array, plus the prepare flag in the query options:

javascript
// Use query markers (?) and parameters
const query = 'UPDATE users SET birth = ? WHERE key=?'; 
const params = [ new Date(1942, 10, 1), 'jimi-hendrix' ];

// Set the prepare flag in the query options
await client.execute(query, params, { prepare: true });
console.log('Row updated on the cluster');

The same `{ prepare: true }` option appears on the batch call, which suggests prepared statements are the expected default rather than an optimisation you opt into later.

eachRow and stream are what make large tables usable

All driver methods use a default `fetchSize` of 5000 rows, retrieving only the first page up to that maximum, and the README gives the reason: to shield an application against accidentally retrieving large result sets in a single response.

5000 is a lot of rows for a naive application holding them in memory as JavaScript objects, and it is small compared with a Cassandra table.

Two methods address that. `eachRow()` and `stream()` parse each row as soon as it is received, yielding rows without buffering them:

javascript
// Reducing a large result
client.eachRow(
  'SELECT time, val FROM temperature WHERE station_id=',
  ['abc'],
  (n, row) => {
    // The callback will be invoked per each row as soon as they are received
    minTemperature = Math.min(row.val, minTemperature); 
  },
  err => { 
    // This function will be invoked when all rows where consumed or an error was encountered  
  }
);

`stream()` works the same way but returns a Node Readable stream in object mode emitting `Row` instances. It can be piped downstream and provides automatic pause and resume logic, buffering when it is not read.

Paging connects the two. `stream()` automatically fetches the next page once the current one is read, and `eachRow()` does the same through the `autoPage` flag.

So the paging behaviour is not something you write. If you iterate rows, the driver fetches the next 5000 rows for you, and the application works with one row at a time regardless of how large the partition is.

UDTs arrive as objects, and batch spans column families

User defined types are represented as JavaScript objects, so a composite column is not a string to be parsed.

The README gives a CQL example where an `address` type holds street, city, state, zip and a set of phones, and a `users` table with an `address frozen<address>` column. Retrieving it gives a nested object, and the example reads `address.street`, `address.city` and `address.state` directly from the row.

Batching is the other structural feature. Multiple statements can be executed in a batch to update or insert several rows atomically even in different column families, and the example passes an array of query and params objects to `client.batch()` with the prepare flag:

javascript
await client.batch(queries, { prepare: true });
console.log('Data updated on cluster');

That last property is the one to think about before using it. A batch spanning column families is atomic, which sounds like a transaction, and it is the closest thing to one that Cassandra offers. But the driver documentation does not frame it as a general transaction primitive here, and the batch statements run against the same cluster in a way that a reader expecting two-phase commit semantics should verify for their own use case rather than assume.

The rest of the surface is in the documentation index rather than the README: the CQL types to JavaScript types mapping page, the API docs, and the FAQ. The examples directory has separate folders for basic, concurrent executions, geotypes, graph, mapper, metadata, tracing, tuple and UDT, with a runner script, which is where each feature has a working demonstration.

Editorial conclusion

Adopt cassandra-driver if your data is in Cassandra and you want the binary protocol, prepared statements and streaming rather than a query layer over HTTP. Skip it if you want a schema or migrations,. Verify first which Cassandra version your cluster runs and whether you need the DSE or Astra specific configuration, since the secure-connect bundle path shown in the README only applies to Astra DB.

Frequently asked questions

How do I install the Cassandra Node.js driver?

Run `npm install cassandra-driver`. The package is `cassandra-driver` at version 4.10.0, licensed Apache-2.0, with types at `index.d.ts` and only two runtime dependencies, adm-zip and long.

Why should I use prepared statements?

Besides skipping re-parsing on the nodes, preparing makes the driver fetch the parameter types from the cluster, which allows an accurate mapping between a JavaScript type and a Cassandra type. Pass the query with `?` markers and set `{ prepare: true }` in the query options.

How does the driver avoid loading a whole table into memory?

All methods use a default fetchSize of 5000 rows, and `eachRow()` and `stream()` parse rows as they arrive without buffering. `stream()` returns a Node Readable in object mode with automatic pause and resume, and both methods fetch the following page automatically, `stream()` directly and `eachRow()` through the `autoPage` flag.

Official sources

  1. apache/cassandra-nodejs-driver on GitHub
  2. Issues
  3. License: Apache-2.0
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/apache-cassandra-nodejs-driver.svg)](https://hysenlabs.com/projects/apache-cassandra-nodejs-driver)

Was this useful?