Build a Node.js Application with Cassandra
Connect Apache Cassandra to a Node.js application in about 10 minutes. You will build a simple user management app that creates a keyspace and table, inserts records using prepared statements, queries them, and cleans up.
What You’ll Build
A command-line Node.js script that:
-
Connects to a local Cassandra instance using the Apache Cassandra Node.js driver
-
Creates a keyspace and a
userstable -
Inserts, queries, updates, and deletes rows using CQL and prepared statements
-
Shuts down cleanly
Start Cassandra
Pull and start a single Cassandra node:
docker pull cassandra:latest
docker run --name cassandra -d -p 9042:9042 cassandra:latest
Wait 30-60 seconds for the node to initialize, then verify readiness:
docker exec cassandra cqlsh -e "DESCRIBE KEYSPACES"
Repeat the command until it returns a list of system keyspaces without an error.
| The Node.js driver handles connection pooling and reconnection automatically, so you do not need to manage socket lifecycle in application code. |
Initialize Your Project
mkdir cassandra-quickstart && cd cassandra-quickstart
npm init -y
npm install cassandra-driver
The cassandra-driver package is the Apache Cassandra Node.js Driver.
It supports all major Cassandra features including prepared statements, token-aware routing, automatic paging, and speculative execution.
Connect to Cassandra
Create a file named app.js and add the following connection setup:
const cassandra = require('cassandra-driver');
const client = new cassandra.Client({
contactPoints: ['127.0.0.1'],
localDataCenter: 'datacenter1'
});
async function main() {
await client.connect();
console.log('Connected to Cassandra!');
}
main().catch(console.error);
Run it to confirm the connection works:
node app.js
You should see Connected to Cassandra! printed to the console.
contactPoints lists one or more seed addresses.
localDataCenter must match the data center name reported by system.local.
For a single-node Docker instance the default is datacenter1.
Create a Keyspace and Table
Replace the body of main() with the following.
The connection call is still required first.
async function main() {
await client.connect();
console.log('Connected to Cassandra!');
// Create keyspace
await client.execute(`
CREATE KEYSPACE IF NOT EXISTS quickstart
WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}
`);
console.log('Keyspace ready.');
// Create table
await client.execute(`
CREATE TABLE IF NOT EXISTS quickstart.users (
id uuid PRIMARY KEY,
name text,
email text
)
`);
console.log('Table ready.');
}
SimpleStrategy with replication_factor: 1 is appropriate for local development.
Use NetworkTopologyStrategy in production clusters.
The examples use random UUIDs because the row ID is only a lookup key for later updates and deletes.
Insert Data
Add an insert function below the table creation block.
The driver auto-prepares statements when you pass { prepare: true }.
// Insert rows
const insertQuery = 'INSERT INTO quickstart.users (id, name, email) VALUES (?, ?, ?)';
const alice = cassandra.types.Uuid.random();
await client.execute(insertQuery, [alice, 'Alice', 'alice@example.com'], { prepare: true });
const bob = cassandra.types.Uuid.random();
await client.execute(insertQuery, [bob, 'Bob', 'bob@example.com'], { prepare: true });
console.log('Inserted 2 rows.');
Pass { prepare: true } to auto-prepare statements for better performance. The driver caches prepared statement IDs per host, so subsequent executions skip the prepare round-trip.
|
cassandra.types.Uuid.random() generates a version 4 UUID.
You can also use cassandra.types.TimeUuid.now() for time-ordered UUIDs.
Query Data
// Read all rows
const result = await client.execute('SELECT * FROM quickstart.users');
console.log('Users:');
for (const row of result.rows) {
console.log(` ${row.name} <${row.email}> (${row.id})`);
}
For large tables use eachRow or pass { fetchSize: N } to control page size.
The driver handles automatic paging when you iterate result.rows across pages.
Update and Delete
// Update Alice's email
await client.execute(
'UPDATE quickstart.users SET email = ? WHERE id = ?',
['alice@newdomain.com', alice],
{ prepare: true }
);
console.log('Updated Alice.');
// Delete Bob
await client.execute(
'DELETE FROM quickstart.users WHERE id = ?',
[bob],
{ prepare: true }
);
console.log('Deleted Bob.');
Complete Application
Here is the full app.js script with a proper shutdown in the finally block:
const cassandra = require('cassandra-driver');
const client = new cassandra.Client({
contactPoints: ['127.0.0.1'],
localDataCenter: 'datacenter1'
});
async function main() {
await client.connect();
console.log('Connected to Cassandra!');
// Keyspace
await client.execute(`
CREATE KEYSPACE IF NOT EXISTS quickstart
WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}
`);
// Table
await client.execute(`
CREATE TABLE IF NOT EXISTS quickstart.users (
id uuid PRIMARY KEY,
name text,
email text
)
`);
const insertQuery = 'INSERT INTO quickstart.users (id, name, email) VALUES (?, ?, ?)';
const alice = cassandra.types.Uuid.random();
const bob = cassandra.types.Uuid.random();
await client.execute(insertQuery, [alice, 'Alice', 'alice@example.com'], { prepare: true });
await client.execute(insertQuery, [bob, 'Bob', 'bob@example.com'], { prepare: true });
console.log('Inserted 2 rows.');
// Read
const result = await client.execute('SELECT * FROM quickstart.users');
console.log('Users:');
for (const row of result.rows) {
console.log(` ${row.name} <${row.email}> (${row.id})`);
}
// Update
await client.execute(
'UPDATE quickstart.users SET email = ? WHERE id = ?',
['alice@newdomain.com', alice],
{ prepare: true }
);
console.log('Updated Alice.');
// Delete
await client.execute(
'DELETE FROM quickstart.users WHERE id = ?',
[bob],
{ prepare: true }
);
console.log('Deleted Bob.');
// Confirm final state
const final = await client.execute('SELECT * FROM quickstart.users');
console.log('Final users:');
for (const row of final.rows) {
console.log(` ${row.name} <${row.email}>`);
}
}
main()
.catch(console.error)
.finally(() => client.shutdown());
Run the complete script:
node app.js
Expected output:
Connected to Cassandra! Inserted 2 rows. Users: Alice <alice@example.com> (...) Bob <bob@example.com> (...) Updated Alice. Deleted Bob. Final users: Alice <alice@newdomain.com>
This quickstart also works with Cassandra 6. When you are ready to go beyond basic CRUD, see ACID Transactions for BEGIN TRANSACTION and Constraints for schema validation.
|
What’s Next
-
Data Modeling — design tables around your query patterns before writing production schemas
-
ACID Transactions — use
BEGIN TRANSACTIONfor lightweight multi-statement atomicity in Cassandra 6 -
Choose a Driver — compare all supported language drivers and their feature matrices
-
Vector Search — store and query vector embeddings alongside your application data