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.