Claude Skills Guide

Claude Code for Turso SQLite Edge Database Tutorial

Turso provides a distributed SQLite database optimized for edge computing When combined with Claude Code, you get an AI-assisted workflow for building applications that need low-latency data access worldwide. This tutorial covers setting up Turso, connecting it to your project, and using Claude’s capabilities to accelerate development.

Why Turso for Edge Computing?

Edge computing demands databases that are lightweight, fast to start, and capable of running close to users worldwide. Turso is libSQL, an open-source fork of SQLite designed specifically for edge computing and distributed databases. Unlike traditional SQLite, Turso offers replication, edge deployment capabilities, and a cloud-managed platform while maintaining SQLite’s simplicity. It addresses edge needs through:

Setting Up Turso SQLite

Before integrating with Claude Code, you need a Turso database instance. Install the Turso CLI and create your first database:

# Install Turso CLI
curl -sSfL https://get.tur.so/install.sh | bash

# Authenticate
turso auth signup

# Create a new database
turso db create my-edge-app

# Get connection string
turso db show my-edge-app --url

The connection string follows the libSQL format: libsql://your-database.turso.io. Turso offers both HTTP and WebSocket connections, with the latter providing better performance for interactive applications.

Connecting to Your Project

Most Node.js projects use the libsql client library. Initialize the connection in your project:

import { createClient } from "@libsql/client";

const client = createClient({
  url: process.env.TURSO_DATABASE_URL,
  authToken: process.env.TURSO_AUTH_TOKEN,
});

// Test the connection
const result = await client.execute("SELECT 1");
console.log("Connected:", result);

For TypeScript projects, add type definitions:

npm install @libsql/client @types/node

Claude Code can help you generate proper type definitions for your tables. Describe your data model and ask Claude to write the schema and TypeScript interfaces.

Schema Design with Claude

When designing your database schema, use Claude’s skills for structured thinking. The /tdd skill proves particularly useful here—you can define your data requirements first, then generate migrations that satisfy those requirements.

Suppose you need a user authentication system:

/tdd
Design a SQLite schema for users, sessions, and API keys.
- Users need: id, email, password_hash, created_at, updated_at
- Sessions need: id, user_id, token, expires_at
- API keys need: id, user_id, key_hash, name, last_used_at
Include foreign keys and appropriate indexes.

Claude generates the migration:

CREATE TABLE users (
  id TEXT PRIMARY KEY,
  email TEXT UNIQUE NOT NULL,
  password_hash TEXT NOT NULL,
  created_at TEXT DEFAULT (datetime('now')),
  updated_at TEXT DEFAULT (datetime('now'))
);

CREATE TABLE sessions (
  id TEXT PRIMARY KEY,
  user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
  token TEXT UNIQUE NOT NULL,
  expires_at TEXT NOT NULL
);

CREATE INDEX idx_sessions_user_id ON sessions(user_id);
CREATE INDEX idx_sessions_token ON sessions(token);

CREATE TABLE api_keys (
  id TEXT PRIMARY KEY,
  user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
  key_hash TEXT NOT NULL,
  name TEXT NOT NULL,
  last_used_at TEXT
);

CREATE INDEX idx_api_keys_user_id ON api_keys(user_id);

Query Building Patterns

Turso works well with ORMs like Drizzle or Prisma. Drizzle provides particularly lightweight SQL generation that pairs well with edge deployments. Here’s how to set up Drizzle with Turso:

npm install drizzle-kit drizzle-orm @libsql/client

Define your schema in src/db/schema.ts:

import { sqliteTable, text } from 'drizzle-orm/sqlite-core';
import { sql } from 'drizzle-orm';

export const users = sqliteTable('users', {
  id: text('id').primaryKey(),
  email: text('email').unique().notNull(),
  passwordHash: text('password_hash').notNull(),
  createdAt: text('created_at').default(sql`datetime('now')`),
  updatedAt: text('updated_at').default(sql`datetime('now')`),
});

export const sessions = sqliteTable('sessions', {
  id: text('id').primaryKey(),
  userId: text('user_id').references(() => users.id).notNull(),
  token: text('token').unique().notNull(),
  expiresAt: text('expires_at').notNull(),
});

Generate migrations with Drizzle Kit:

npx drizzle-kit generate:sqlite
npx drizzle-kit push:sqlite

Python Client with Async Support

For Python projects, Claude Code can generate an async-ready Turso client with proper error handling:

import libsql_client
from libsql_client import ResultSet
import asyncio
from typing import Optional

class TursoDB:
    def __init__(self, database_url: str, auth_token: Optional[str] = None):
        self.database_url = database_url
        self.auth_token = auth_token
        self._client: Optional[libsql_client.Client] = None

    async def connect(self):
        self._client = await libsql_client.connect(
            url=self.database_url,
            auth_token=self.auth_token
        )

    async def execute(self, query: str, parameters: list = None) -> ResultSet:
        if not self._client:
            await self.connect()
        return await self._client.execute(query, parameters or [])

    async def close(self):
        if self._client:
            await self._client.close()

With this client, Claude Code can generate parameterized CRUD functions for any table. For example, an upsert-on-conflict pattern for a user preferences table:

async def insert_user_preference(db: TursoDB, user_id: str, key: str, value: str, edge_location: str) -> dict:
    result = await db.execute(
        """INSERT INTO user_preferences (user_id, preference_key, preference_value, edge_location)
           VALUES (?, ?, ?, ?)
           ON CONFLICT(user_id, preference_key, edge_location)
           DO UPDATE SET preference_value = excluded.preference_value,
                         updated_at = strftime('%s', 'now')""",
        [user_id, key, value, edge_location]
    )
    return {"success": True, "last_insert_rowid": result.last_insert_rowid}

Edge Replication Patterns

One of Turso’s strengths is its ability to replicate data to edge locations. Claude Code can help you design effective replication strategies.

Edge-First Writes

For applications that write at the edge and sync later, Claude Code can scaffold an EdgeFirstWriter class that writes to local replicas first, then syncs to the primary:

from datetime import datetime

class EdgeFirstWriter:
    def __init__(self, edge_db: TursoDB, primary_db: TursoDB):
        self.edge_db = edge_db
        self.primary_db = primary_db

    async def write_local(self, table: str, data: dict) -> dict:
        data['_edge_written_at'] = int(datetime.now().timestamp())
        data['_synced'] = 0

        columns = ', '.join(data.keys())
        placeholders = ', '.join(['?' for _ in data])

        await self.edge_db.execute(
            f"INSERT INTO {table} ({columns}) VALUES ({placeholders})",
            list(data.values())
        )
        return {"status": "local_write", "sync_pending": True}

    async def sync_to_primary(self, table: str, local_id: int) -> dict:
        result = await self.edge_db.execute(
            f"SELECT * FROM {table} WHERE id = ? AND _synced = 0",
            [local_id]
        )
        if not result.rows:
            return {"status": "already_synced"}

        record = dict(result.rows[0])
        del record['id']
        await self.primary_db.execute(
            f"INSERT INTO {table} ({', '.join(record.keys())}) VALUES ({', '.join(['?' for _ in record])})",
            list(record.values())
        )
        await self.edge_db.execute(
            f"UPDATE {table} SET _synced = 1 WHERE id = ?", [local_id]
        )
        return {"status": "synced", "table": table, "local_id": local_id}

Handling Sync Conflicts

When multiple edge locations update the same record, a last-write-wins resolver using updated_at timestamps keeps data consistent:

async def resolve_conflict(edge_db: TursoDB, user_id: str, preference_key: str) -> dict:
    result = await edge_db.execute(
        """SELECT * FROM user_preferences
           WHERE user_id = ? AND preference_key = ?
           ORDER BY updated_at DESC""",
        [user_id, preference_key]
    )
    versions = [dict(row) for row in result.rows]
    if len(versions) <= 1:
        return {"resolved": True, "chosen_version": versions[0] if versions else None}

    winner = versions[0]
    await edge_db.execute(
        """DELETE FROM user_preferences
           WHERE user_id = ? AND preference_key = ? AND id != ?""",
        [user_id, preference_key, winner['id']]
    )
    return {
        "resolved": True,
        "chosen_version": winner,
        "conflicts_resolved": len(versions) - 1
    }

Edge Function Integration

Deploying to edge runtimes like Cloudflare Workers or Vercel Edge requires specific handling. Turso’s HTTP endpoint works with these environments:

// workers/my-worker.ts
import { createClient } from "@libsql/client/web";

export default {
  async fetch(request: Request): Promise<Response> {
    const client = createClient({
      url: TURSO_URL,
      authToken: TURSO_TOKEN,
    });

    const users = await client.execute(
      "SELECT id, email FROM users LIMIT 10"
    );

    return new Response(JSON.stringify(users.rows), {
      headers: { "Content-Type": "application/json" },
    });
  },
};

The web client uses fetch internally, making it compatible with any environment that supports the Web Fetch API.

Using Claude Skills for Database Tasks

Several Claude skills enhance database development workflows:

The supermemory skill stores schema documentation and business rules, making it easier to maintain consistency as your application evolves.

Performance Considerations

Turso’s edge replication significantly reduces latency, but you should still optimize query patterns:

-- Use covering indexes for frequent reads
CREATE INDEX idx_users_email_covering 
ON users(email) INCLUDE (id, password_hash);

-- Batch inserts for bulk operations
INSERT INTO users (id, email, password_hash) VALUES 
  (?, ?, ?), (?, ?, ?), (?, ?, ?);

Monitor query performance using Turso’s dashboard or add logging:

const start = Date.now();
const result = await client.execute(query);
console.log(`Query took ${Date.now() - start}ms`);

Best Practices for Claude Code + Turso

When working with Turso and Claude Code, keep these practices in mind:

  1. Use parameterized queries: Always use ? placeholders instead of string interpolation to prevent SQL injection
  2. Batch operations: Use bulk inserts when possible to reduce round trips
  3. Index strategically: Create indexes on columns used in WHERE clauses and covering indexes for frequent reads
  4. Handle connection pooling: Reuse connections when possible for better performance
  5. Implement retry logic: Network requests to edge locations can fail; handle transient errors gracefully
  6. Monitor query performance: Log query durations and use Turso’s dashboard to spot slow queries early

Next Steps

With your Turso database connected to Claude Code, you have a powerful setup for building edge applications. The AI-assisted workflow handles everything from schema design to migration management, letting you focus on application logic.

Explore embedding Turso in serverless functions, implementing real-time subscriptions with WebSockets, or adding row-level security for multi-tenant applications. Claude Code’s skills like pdf can help generate documentation for your data layer, while frontend-design skills ensure your application UI properly integrates with your backend.

Built by theluckystrike — More at zovo.one