Claude Code for Turso: Edge SQLite with libSQL and Global Replication — Claude Skills 360 Blog
Blog / Backend / Claude Code for Turso: Edge SQLite with libSQL and Global Replication
Backend

Claude Code for Turso: Edge SQLite with libSQL and Global Replication

Published: January 24, 2027
Read time: 8 min read
By: Claude Skills 360

Turso brings SQLite to production with global replication, sub-millisecond reads from edge replicas, and HTTP-accessible databases. libSQL extends SQLite with a network protocol — the same Drizzle and SQLite queries work locally and against Turso. Embedded replicas run SQLite in-process with automatic sync to primary: reads hit local memory, writes sync to Turso. Multi-tenant architectures provision one database per tenant — hundreds of thousands of databases, each free-tier priced. Turso’s HTTP API works from edge functions where native bindings aren’t available. Claude Code generates Turso database schemas, libSQL client configurations, Drizzle migrations, multi-tenant provisioning scripts, and the edge function deployment patterns for global applications.

CLAUDE.md for Turso Projects

## Turso Stack
- Client: @libsql/client >= 0.14 — createClient({ url, authToken })
- ORM: drizzle-orm >= 0.34 with drizzle-orm/libsql dialect
- Migrations: drizzle-kit generate + turso db shell < migration.sql
- Embedded replica: url: "file:local.db", syncUrl: TURSO_URL — syncs on startup
- Multi-tenant: one database per tenant, provision with Turso Platform API
- Edge: use HTTP client (@libsql/client with fetch) for CF Workers/Vercel Edge
- Branching: turso db create --from-db prod-db for staging/preview

Basic libSQL Client

// lib/db.ts — Turso libSQL client setup
import { createClient } from "@libsql/client"
import { drizzle } from "drizzle-orm/libsql"
import * as schema from "./schema"

// Production: remote Turso database
const remoteClient = createClient({
  url: process.env.TURSO_DATABASE_URL!,
  authToken: process.env.TURSO_AUTH_TOKEN!,
})

// Embedded replica: fast local reads, sync writes to remote
const embeddedClient = createClient({
  url: "file:local.db",                     // Local SQLite file
  syncUrl: process.env.TURSO_DATABASE_URL!, // Turso remote for writes
  authToken: process.env.TURSO_AUTH_TOKEN!,
})

// Choose based on environment
const client = process.env.USE_EMBEDDED_REPLICA === "true"
  ? embeddedClient
  : remoteClient

export const db = drizzle(client, { schema })

// Sync embedded replica on startup (optional)
export async function syncDatabase() {
  if (client.sync) {
    await client.sync()
    console.log("Database synced with Turso")
  }
}

Drizzle Schema

// lib/schema.ts — Drizzle ORM SQLite schema
import { sqliteTable, text, integer, real } from "drizzle-orm/sqlite-core"
import { sql } from "drizzle-orm"

export const orders = sqliteTable("orders", {
  id: text("id").primaryKey(),
  customerId: text("customer_id").notNull(),
  status: text("status", {
    enum: ["pending", "processing", "shipped", "delivered", "cancelled"]
  }).notNull().default("pending"),
  totalCents: integer("total_cents").notNull(),
  createdAt: text("created_at")
    .notNull()
    .default(sql`(datetime('now'))`),
  updatedAt: text("updated_at")
    .notNull()
    .default(sql`(datetime('now'))`),
})

export const orderItems = sqliteTable("order_items", {
  id: text("id").primaryKey(),
  orderId: text("order_id")
    .notNull()
    .references(() => orders.id, { onDelete: "cascade" }),
  productId: text("product_id").notNull(),
  productName: text("product_name").notNull(),
  quantity: integer("quantity").notNull(),
  priceCents: integer("price_cents").notNull(),
})

export const customers = sqliteTable("customers", {
  id: text("id").primaryKey(),
  email: text("email").notNull().unique(),
  name: text("name").notNull(),
  tier: text("tier", { enum: ["free", "pro", "enterprise"] })
    .notNull()
    .default("free"),
  createdAt: text("created_at")
    .notNull()
    .default(sql`(datetime('now'))`),
})

Drizzle Queries

// lib/queries.ts — type-safe Drizzle queries for Turso
import { db } from "./db"
import { orders, orderItems, customers } from "./schema"
import { eq, desc, and, sql, inArray } from "drizzle-orm"
import { nanoid } from "nanoid"

export async function createOrder(
  customerId: string,
  items: { productId: string; productName: string; quantity: number; priceCents: number }[]
) {
  const totalCents = items.reduce(
    (sum, item) => sum + item.priceCents * item.quantity,
    0
  )
  const orderId = nanoid()

  // Drizzle transaction
  await db.transaction(async (tx) => {
    await tx.insert(orders).values({
      id: orderId,
      customerId,
      totalCents,
      status: "pending",
    })

    await tx.insert(orderItems).values(
      items.map((item) => ({
        id: nanoid(),
        orderId,
        ...item,
      }))
    )
  })

  return orderId
}

export async function getOrderWithItems(orderId: string) {
  const order = await db.query.orders.findFirst({
    where: eq(orders.id, orderId),
    with: {
      items: true,   // Requires schema relations
    },
  })

  return order
}

export async function listCustomerOrders(
  customerId: string,
  { status, limit = 20, offset = 0 }:
  { status?: string; limit?: number; offset?: number } = {}
) {
  let query = db
    .select()
    .from(orders)
    .where(
      and(
        eq(orders.customerId, customerId),
        status ? eq(orders.status, status as any) : undefined,
      )
    )
    .orderBy(desc(orders.createdAt))
    .limit(limit)
    .offset(offset)

  return query
}

export async function getOrderStats(customerId: string) {
  const [stats] = await db
    .select({
      totalOrders: sql<number>`count(*)`,
      totalSpentCents: sql<number>`sum(total_cents)`,
      pendingCount: sql<number>`sum(case when status = 'pending' then 1 else 0 end)`,
    })
    .from(orders)
    .where(eq(orders.customerId, customerId))

  return stats
}

Multi-Tenant Database Provisioning

// lib/tenant.ts — provision a database per tenant
const TURSO_API = "https://api.turso.tech/v1"
const ORG_NAME = process.env.TURSO_ORG_NAME!
const API_TOKEN = process.env.TURSO_PLATFORM_TOKEN!

interface TursoDatabase {
  name: string
  hostname: string
  token: string
}

async function provisionTenantDatabase(tenantId: string): Promise<TursoDatabase> {
  const dbName = `tenant-${tenantId}`

  // Create database
  const createResponse = await fetch(`${TURSO_API}/organizations/${ORG_NAME}/databases`, {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${API_TOKEN}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      name: dbName,
      group: "default",    // Named group for colocation
    }),
  })

  const { database } = await createResponse.json()

  // Create auth token for this tenant's database
  const tokenResponse = await fetch(
    `${TURSO_API}/organizations/${ORG_NAME}/databases/${dbName}/auth/tokens`,
    {
      method: "POST",
      headers: { "Authorization": `Bearer ${API_TOKEN}` },
    }
  )

  const { jwt } = await tokenResponse.json()

  // Run schema migrations on new tenant database
  const tenantClient = createClient({
    url: `libsql://${database.hostname}`,
    authToken: jwt,
  })

  const migrations = readMigrationsFromDir("./drizzle")
  for (const migration of migrations) {
    await tenantClient.execute(migration)
  }

  return {
    name: dbName,
    hostname: database.hostname,
    token: jwt,
  }
}

// Get per-tenant db instance (with connection pooling/caching)
const tenantClients = new Map<string, ReturnType<typeof drizzle>>()

export function getTenantDb(tenantUrl: string, authToken: string) {
  const key = tenantUrl

  if (!tenantClients.has(key)) {
    const client = createClient({ url: tenantUrl, authToken })
    tenantClients.set(key, drizzle(client, { schema }))
  }

  return tenantClients.get(key)!
}

Edge Function with Turso HTTP

// src/worker.ts — Cloudflare Worker with Turso HTTP client
import { createClient } from "@libsql/client/web"    // HTTP-only client for edge
import { drizzle } from "drizzle-orm/libsql"
import * as schema from "./schema"

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    // Create HTTP client per request (no persistent connections at edge)
    const client = createClient({
      url: env.TURSO_URL,
      authToken: env.TURSO_TOKEN,
    })

    const db = drizzle(client, { schema })

    const url = new URL(request.url)

    if (url.pathname === "/orders" && request.method === "GET") {
      const customerId = url.searchParams.get("customer_id")
      if (!customerId) return new Response("Missing customer_id", { status: 400 })

      const orders = await listCustomerOrders(customerId)
      return Response.json(orders)
    }

    return new Response("Not found", { status: 404 })
  }
}

For the PlanetScale / Neon serverless PostgreSQL alternatives that offer similar branching workflows with PostgreSQL semantics instead of SQLite, see the database migrations guide for multi-environment migration strategies. For the SQLite in production patterns with WAL mode and backup strategies beyond Turso, the SQLite production guide covers optimization and replication. The Claude Skills 360 bundle includes Turso skill sets covering libSQL setup, multi-tenant provisioning, and edge function deployment. Start with the free tier to try Turso database generation.

Keep Reading

Backend

Claude Code for Bun: Fast JavaScript Runtime and Toolkit

Build with Bun and Claude Code — Bun.serve for HTTP servers, Bun.file for fast file I/O, Bun.$ for shell commands, Bun.sql for SQLite and PostgreSQL, Bun.build for bundling, bun:test for testing, Bun.hash for hashing, bun.lock for deterministic installs, bun run for package.json scripts, hot reloading with --hot, bun init for project scaffolding, and compatibility with Node.js modules.

6 min read Jun 13, 2027
Backend

Claude Code for Express.js Advanced: Patterns for Production APIs

Advanced Express.js patterns with Claude Code — typed request handlers with RequestHandler generics, async error handling middleware, Zod validation middleware factory, rate limiting with express-rate-limit and Redis store, helmet security middleware, compression, dependency injection with tsyringe, file upload with multer and S3, pagination utilities, JWT middleware, and structured logging with pino.

6 min read Jun 8, 2027
Backend

Claude Code for KeystoneJS: Node.js CMS and App Framework

Build full-stack apps with KeystoneJS and Claude Code — config with lists, fields.text and fields.relationship for schema definition, access control with isAuthenticated and isAdmin functions, hooks with beforeOperation and afterOperation, GraphQL API auto-generation from schema, AdminUI for content management, session with statelessSessions, Prisma adapter for database, file storage with images and files fields, and custom REST endpoints.

6 min read Jun 7, 2027

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free