Skip to content
Learni
View all tutorials
Google Cloud

How to Get Started with Cloud Spanner in 2026

Lire en français

Introduction

Google Cloud Spanner is a fully managed relational database that scales horizontally at global level, with full ACID compliance and strong consistency. Unlike traditional databases like PostgreSQL that struggle beyond a few TB, Spanner handles petabytes without downtime—ideal for fintech, e-commerce, or IoT apps needing high availability.

This beginner tutorial guides you step by step: from installing the SDK to running SQL queries via Node.js. You'll learn to create an instance, a database, tables, insert data, and query in real time. By the end, you'll have a fully functional project you can bookmark. Allow 30-45 minutes and a free GCP account (300$ credit). Why 2026? New features like Gemini integration for AI-powered queries and optimized developer pricing.

Key benefits: Global latency under 10ms, auto-scaling, automated backups. Perfect for replacing Cassandra or BigQuery in OLTP workloads.

Prerequisites

  • Active Google Cloud Platform (GCP) account with billing enabled (300$ free credit).
  • Node.js 20+ installed.
  • gcloud CLI version 450+.
  • Basic TypeScript/JavaScript and SQL knowledge.
  • Code editor (VS Code recommended).

Enable the API and Install the SDK

terminal-setup.sh
# Authenticate gcloud

gcloud auth login

gcloud config set project YOUR-PROJECT-ID  # Replace with your GCP project ID

gcloud services enable spanner.googleapis.com

gcloud components install beta

# Install the Node.js client
mkdir spanner-tutorial && cd spanner-tutorial
npm init -y
npm install @google-cloud/spanner typescript @types/node ts-node

# Compile TS (optional)
npm install -D typescript

This script enables the Spanner API, authenticates gcloud, and sets up a Node.js project with the official client. Replace YOUR-PROJECT-ID with your GCP project ID (found in console.cloud.google.com). Common pitfalls: forgetting auth or project ID leads to 403/404 errors.

Set Up the Environment

Once you've run the bash setup, verify with gcloud projects list. Download service account credentials if needed (IAM > Service Accounts > Create JSON key), but gcloud auth works fine for local dev. Create a .env file for IDs: PROJECT_ID=yourproject, INSTANCE_ID=test-instance, DATABASE_ID=test-db. Spanner is multi-regional by default for high availability.

Create the Spanner Instance

create-instance.sh
# Create a regional instance (config=1 for 1000 processing units, adjust for prod)
gcloud spanner instances create $INSTANCE_ID \
  --config=nam-eur3 \
  --description="Tutorial instance" \
  --nodes=1

# Verify
sleep 60
gcloud spanner instances describe $INSTANCE_ID

# Environment variables (add to .env)
export INSTANCE_ID=test-instance

Creates a minimal instance (1 node, ~$100/month, free in dev). nam-eur3 is Europe; list configs with gcloud spanner instance-configs list. Pitfall: Provisioning takes 1-2 minutes; skipping the wait causes timeouts.

Create the Database

The instance is the scalable container; the database lives inside it logically. Use DDL SQL for schemas. Think of Spanner as SQL + NoSQL: interleaved tables for hierarchies (e.g., users > orders).

Create the Database and Tables

create-db.sh
# Create the DB
gcloud spanner databases create $DATABASE_ID --instance=$INSTANCE_ID --ddl='CREATE TABLE Users (
  UserId    INT64 NOT NULL,
  Email     STRING(256) NOT NULL,
  Name      STRING(100),
) PRIMARY KEY(UserId)' \
  --ddl='CREATE TABLE Orders (
  OrderId     INT64 NOT NULL,
  UserId      INT64 NOT NULL,
  Product     STRING(100),
  Amount      FLOAT64,
) PRIMARY KEY(OrderId),
  INTERLEAVE IN PARENT Users ON DELETE CASCADE'

# Verify
databases list --instance=$INSTANCE_ID

Creates a DB with Users and Orders tables. Interleave links Orders to Users for fast, low-latency joins. Use FLOAT64 for amounts; NOT NULL avoids nulls. Pitfall: Strict DDL syntax—test in the Spanner console first.

Node.js Client for Insertions

Analogy: Like a database driver, the client handles sessions and transactions. Spanner supports batch mutations for top performance.

Insert Data

insert-data.ts
import { Spanner } from '@google-cloud/spanner';

const spanner = new Spanner({
  projectId: process.env.PROJECT_ID!,
});

const instance = spanner.instance(process.env.INSTANCE_ID!);
const database = instance.database(process.env.DATABASE_ID!);

async function insertData() {
  const tx = await database.runTransaction();
  try {
    await tx.insert('Users', {
      UserId: 1n,
      Email: 'alice@example.com',
      Name: 'Alice',
    });
    await tx.insert('Orders', {
      OrderId: 101n,
      UserId: 1n,
      Product: 'Laptop',
      Amount: 999.99,
    });
    await tx.commit();
    console.log('Données insérées');
  } catch (err) {
    await tx.rollback();
    console.error(err);
  }
}

insertData();

Inserts data in an ACID transaction. Use BigInt (1n) for INT64. RunTransaction ensures consistency. Pitfall: Forgetting commit/rollback causes crashes; always use async/await.

Run Queries

Standard SQL queries plus Spanner extensions (ARRAY, STRUCT). Use pagination with LIMIT/OFFSET for massive scale.

Read and Query Data

query-data.ts
import { Spanner } from '@google-cloud/spanner';

const spanner = new Spanner({ projectId: process.env.PROJECT_ID! });
const instance = spanner.instance(process.env.INSTANCE_ID!);
const database = instance.database(process.env.DATABASE_ID!);

async function queryData() {
  const [[usersRow]] = await database.run({
    sql: `SELECT u.UserId, u.Name, ARRAY_AGG(o.Product) AS Products
          FROM Users u
          LEFT JOIN Orders o ON u.UserId = o.UserId
          GROUP BY u.UserId, u.Name`,
  });
  console.log('Users et orders:', usersRow);

  // Single row
  const [order] = await database.run({
    sql: 'SELECT * FROM Orders WHERE OrderId = @id',
    params: { id: 101n },
    types: { id: 'INT64' },
  });
  console.log('Order:', order);
}

queryData();

JOIN query with ARRAY_AGG (Spanner-specific). Params and types prevent SQL injection. Pitfall: Types are required for params; omitting them causes parsing errors.

Cleanup and Full Script

full-app.ts
import { Spanner } from '@google-cloud/spanner';
import dotenv from 'dotenv';
dotenv.config();

const spanner = new Spanner({ projectId: process.env.PROJECT_ID! });
const instance = spanner.instance(process.env.INSTANCE_ID!);
const database = instance.database(process.env.DATABASE_ID!);

async function main() {
  // Insert
  const tx = await database.runTransaction();
  try {
    await tx.insert('Users', { UserId: 2n, Email: 'bob@example.com', Name: 'Bob' });
    await tx.insert('Orders', { OrderId: 102n, UserId: 2n, Product: 'Phone', Amount: 599.99 });
    await tx.commit();
  } catch (err) {
    await tx.rollback();
    console.error('Insert failed:', err);
    return;
  }

  // Query
  const [rows] = await database.run({
    sql: 'SELECT * FROM Users u JOIN Orders o ON u.UserId = o.UserId',
  });
  console.table(rows);
}

main().catch(console.error);

// Run: ts-node full-app.ts

All-in-one script: dotenv for env vars, insert + query. console.table for visualization. Pitfall: .env ignored without dotenv; always add global catch.

Best Practices

  • Always use transactions: Guarantees ACID for multi-mutations.
  • Interleave tables: For 1:N relationships, reduces join latency 10x.
  • Secondary indexes: CREATE INDEX ON Orders(UserId) for fast queries.
  • Monitoring: Use Cloud Monitoring for CPU/QPS; auto-scale nodes.
  • Automated backups: Point-in-time recovery up to 7 days, free.

Common Errors to Avoid

  • Missing param types: Queries crash with 'Invalid type'. Always specify { id: 'INT64' }.
  • No provisioning wait: Instance/DB takes 2-5 min; poll with describe.
  • Quota exceeded: 1 free instance; upgrade for prod.
  • INT64 as number: Use BigInt—JS numbers overflow >2^53.

Next Steps