Build a Go Application with Cassandra

Preview | Unofficial | For review only

Connect a Go application to Cassandra, create a schema, and perform basic CRUD operations. Estimated time: ~10 minutes.

What You’ll Build

A simple user management application that demonstrates how to:

  • Connect to a Cassandra cluster using gocql

  • Create a keyspace and table

  • Insert, query, update, and delete rows

  • Iterate over result sets with a scanner

Prerequisites

  • Go 1.21 or later

  • Docker (to run a local Cassandra node)

Start Cassandra

Start a single-node Cassandra instance in Docker:

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.

Initialize Your Project

Create a new Go module and install the gocql driver:

mkdir cassandra-quickstart && cd cassandra-quickstart
go mod init cassandra-quickstart
go get github.com/gocql/gocql

Create a file named main.go to hold the application.

gocql uses token-aware routing by default for optimal performance. Queries are sent directly to the replica that owns the partition key, reducing inter-node hops.

Connect to Cassandra

Add the following to main.go to establish a session:

package main

import (
	"fmt"
	"log"

	"github.com/gocql/gocql"
)

func main() {
	cluster := gocql.NewCluster("127.0.0.1")
	cluster.Consistency = gocql.Quorum

	session, err := cluster.CreateSession()
	if err != nil {
		log.Fatal(err)
	}
	defer session.Close()

	fmt.Println("Connected to Cassandra!")
}

Run it to verify the connection:

go run main.go

Expected output: Connected to Cassandra!

Create the Keyspace and Table

Add a helper function to set up the schema. Call it from main after the session is opened:

func setupSchema(session *gocql.Session) {
	err := session.Query(`
		CREATE KEYSPACE IF NOT EXISTS quickstart
		WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}
	`).Exec()
	if err != nil {
		log.Fatal(err)
	}

	err = session.Query(`
		CREATE TABLE IF NOT EXISTS quickstart.users (
			id    uuid PRIMARY KEY,
			name  text,
			email text
		)
	`).Exec()
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println("Schema ready.")
}
// Inside main(), after session.Close() is deferred:
setupSchema(session)

Insert Data

Insert a row using a time-based UUID:

This quickstart uses gocql.TimeUUID() so the IDs are time-ordered when you print them. The Java, Python, and Node.js quickstarts use random UUIDs because their examples only need opaque row IDs for later updates and deletes.

func insertUser(session *gocql.Session, name, email string) gocql.UUID {
	id := gocql.TimeUUID()

	if err := session.Query(
		"INSERT INTO quickstart.users (id, name, email) VALUES (?, ?, ?)",
		id, name, email,
	).Exec(); err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Inserted user: %s (%s) with id %s\n", name, email, id)
	return id
}

Call insertUser from main to add a couple of users:

insertUser(session, "Alice", "alice@example.com")
insertUser(session, "Bob", "bob@example.com")

Query Data

Read all rows and scan each one into typed variables:

func listUsers(session *gocql.Session) {
	scanner := session.Query("SELECT id, name, email FROM quickstart.users").
		Iter().Scanner()

	for scanner.Next() {
		var id gocql.UUID
		var name, email string

		if err := scanner.Scan(&id, &name, &email); err != nil {
			log.Fatal(err)
		}

		fmt.Printf("User: %s (%s) — id: %s\n", name, email, id)
	}

	if err := scanner.Err(); err != nil {
		log.Fatal(err)
	}
}

Call listUsers(session) from main after inserting rows to print them.

Update and Delete

Update a Row

func updateEmail(session *gocql.Session, id gocql.UUID, newEmail string) {
	if err := session.Query(
		"UPDATE quickstart.users SET email = ? WHERE id = ?",
		newEmail, id,
	).Exec(); err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Updated email for %s to %s\n", id, newEmail)
}

Delete a Row

func deleteUser(session *gocql.Session, id gocql.UUID) {
	if err := session.Query(
		"DELETE FROM quickstart.users WHERE id = ?",
		id,
	).Exec(); err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Deleted user %s\n", id)
}

Complete Application

Here is the full main.go combining all steps:

package main

import (
	"fmt"
	"log"

	"github.com/gocql/gocql"
)

func main() {
	cluster := gocql.NewCluster("127.0.0.1")
	cluster.Consistency = gocql.Quorum

	session, err := cluster.CreateSession()
	if err != nil {
		log.Fatal(err)
	}
	defer session.Close()

	fmt.Println("Connected to Cassandra!")

	setupSchema(session)

	alice := insertUser(session, "Alice", "alice@example.com")
	insertUser(session, "Bob", "bob@example.com")

	fmt.Println("\n-- All users --")
	listUsers(session)

	updateEmail(session, alice, "alice@updated.example.com")

	fmt.Println("\n-- After update --")
	listUsers(session)

	deleteUser(session, alice)

	fmt.Println("\n-- After delete --")
	listUsers(session)
}

func setupSchema(session *gocql.Session) {
	if err := session.Query(`
		CREATE KEYSPACE IF NOT EXISTS quickstart
		WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}
	`).Exec(); err != nil {
		log.Fatal(err)
	}

	if err := session.Query(`
		CREATE TABLE IF NOT EXISTS quickstart.users (
			id    uuid PRIMARY KEY,
			name  text,
			email text
		)
	`).Exec(); err != nil {
		log.Fatal(err)
	}

	fmt.Println("Schema ready.")
}

func insertUser(session *gocql.Session, name, email string) gocql.UUID {
	id := gocql.TimeUUID()

	if err := session.Query(
		"INSERT INTO quickstart.users (id, name, email) VALUES (?, ?, ?)",
		id, name, email,
	).Exec(); err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Inserted: %s (%s)\n", name, email)
	return id
}

func listUsers(session *gocql.Session) {
	scanner := session.Query("SELECT id, name, email FROM quickstart.users").
		Iter().Scanner()

	for scanner.Next() {
		var id gocql.UUID
		var name, email string

		if err := scanner.Scan(&id, &name, &email); err != nil {
			log.Fatal(err)
		}

		fmt.Printf("  %s (%s) — id: %s\n", name, email, id)
	}

	if err := scanner.Err(); err != nil {
		log.Fatal(err)
	}
}

func updateEmail(session *gocql.Session, id gocql.UUID, newEmail string) {
	if err := session.Query(
		"UPDATE quickstart.users SET email = ? WHERE id = ?",
		newEmail, id,
	).Exec(); err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Updated email for %s to %s\n", id, newEmail)
}

func deleteUser(session *gocql.Session, id gocql.UUID) {
	if err := session.Query(
		"DELETE FROM quickstart.users WHERE id = ?",
		id,
	).Exec(); err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Deleted user %s\n", id)
}

Run the complete application:

go run main.go

You should see output showing the inserts, the full user list, the updated email, and the final list after deletion.

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 partition keys and clustering columns for your access patterns

  • ACID Transactions — use lightweight transactions and batch operations

  • Choose a Driver — explore drivers for other languages and compare features

  • Vector Search — build AI-powered similarity search with vector embeddings