Claude Code for Cloudflare D1: SQLite at the Edge — Claude Skills 360 Blog
Blog / Backend / Claude Code for Cloudflare D1: SQLite at the Edge
Backend

Claude Code for Cloudflare D1: SQLite at the Edge

Published: November 15, 2026
Read time: 7 min read
By: Claude Skills 360

Cloudflare D1 is SQLite running in Cloudflare’s global network. Your Worker runs in 300 edge locations, and D1 provides a database co-located in the same datacenter — eliminating the round-trip to a central database. D1 handles migrations, replication, and point-in-time recovery. The API is the familiar SQLite API: db.prepare(), db.run(), and db.batch() for transactions. Claude Code writes D1-backed Workers, schema migrations, Drizzle ORM integration, and the patterns that handle D1’s consistency model correctly.

CLAUDE.md for Cloudflare D1 Projects

## Edge Stack
- Cloudflare Workers + D1 (SQLite at edge)
- ORM: Drizzle ORM with drizzle-orm/d1 adapter
- Migrations: drizzle-kit generate → apply via wrangler d1 migrations apply
- Binding: DB (D1Database) in wrangler.toml and worker env
- Consistency: D1 is eventually consistent for read replicas; primary write is strong
- Batch: use db.batch([...]) for multiple statements in one round-trip
- Development: wrangler dev with --local for SQLite in .wrangler/state/

Wrangler Configuration

# wrangler.toml
name = "my-api"
main = "src/index.ts"
compatibility_date = "2026-09-01"

[[d1_databases]]
binding = "DB"
database_name = "my-app-db"
database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

[[env.production.d1_databases]]
binding = "DB"
database_name = "my-app-db-production"
database_id = "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy"
migrations_dir = "drizzle"

Schema and Migrations

// src/db/schema.ts — Drizzle schema for D1 (SQLite dialect)
import { sqliteTable, text, integer, blob } from 'drizzle-orm/sqlite-core';
import { sql } from 'drizzle-orm';

export const orders = sqliteTable('orders', {
  id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
  userId: text('user_id').notNull(),
  status: text('status', { enum: ['pending', 'shipped', 'delivered', 'cancelled'] })
    .notNull()
    .default('pending'),
  totalCents: integer('total_cents').notNull(),
  createdAt: integer('created_at', { mode: 'timestamp' })
    .notNull()
    .$defaultFn(() => new Date()),
  metadata: text('metadata', { mode: 'json' }).$type<Record<string, unknown>>(),
});

export const orderItems = sqliteTable('order_items', {
  id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
  orderId: text('order_id').notNull().references(() => orders.id, { onDelete: 'cascade' }),
  productId: text('product_id').notNull(),
  quantity: integer('quantity').notNull(),
  priceCents: integer('price_cents').notNull(),
  productName: text('product_name').notNull(),
});
# Generate migration SQL from schema
npx drizzle-kit generate

# Apply migration to local dev D1
npx wrangler d1 migrations apply my-app-db --local

# Apply to production
npx wrangler d1 migrations apply my-app-db --remote

Worker with D1 Queries

// src/index.ts — Cloudflare Worker with D1
import { drizzle } from 'drizzle-orm/d1';
import { eq, desc, and } from 'drizzle-orm';
import * as schema from './db/schema';

export interface Env {
  DB: D1Database;
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const url = new URL(request.url);
    const db = drizzle(env.DB, { schema });
    
    // Route: GET /orders/:id
    if (request.method === 'GET' && url.pathname.match(/^\/orders\/[\w-]+$/)) {
      const orderId = url.pathname.split('/')[2];
      
      const order = await db.query.orders.findFirst({
        where: eq(schema.orders.id, orderId),
        with: { items: true },
      });
      
      if (!order) return new Response('Not found', { status: 404 });
      
      return Response.json(order, {
        headers: { 'Cache-Control': 'private, max-age=30' },
      });
    }
    
    // Route: POST /orders
    if (request.method === 'POST' && url.pathname === '/orders') {
      const body = await request.json() as { userId: string; items: any[] };
      
      const totalCents = body.items.reduce(
        (sum: number, i: any) => sum + i.quantity * i.priceCents, 0
      );
      
      // D1 transaction: create order + items atomically
      const result = await db.batch([
        db.insert(schema.orders).values({
          userId: body.userId,
          totalCents,
        }).returning(),
        ...body.items.map((item: any) =>
          db.insert(schema.orderItems).values({
            orderId: 'PLACEHOLDER',  // Set in transaction result
            productId: item.productId,
            quantity: item.quantity,
            priceCents: item.priceCents,
            productName: item.productName,
          })
        ),
      ]);
      
      return Response.json(result[0][0], { status: 201 });
    }
    
    // Route: GET /orders?userId=...
    if (request.method === 'GET' && url.pathname === '/orders') {
      const userId = url.searchParams.get('userId');
      
      const userOrders = await db.query.orders.findMany({
        where: userId ? eq(schema.orders.userId, userId) : undefined,
        orderBy: desc(schema.orders.createdAt),
        limit: 20,
        with: { items: true },
      });
      
      return Response.json(userOrders);
    }
    
    return new Response('Not found', { status: 404 });
  },
};

D1 Batch Operations

// Batch: multiple statements, one round-trip to D1
async function batchOrderOps(env: Env, orderId: string, newStatus: string) {
  const results = await env.DB.batch([
    // Update order status
    env.DB.prepare(
      'UPDATE orders SET status = ?, updated_at = ? WHERE id = ?'
    ).bind(newStatus, Date.now(), orderId),
    
    // Log the status change
    env.DB.prepare(
      'INSERT INTO order_events (order_id, event_type, created_at) VALUES (?, ?, ?)'
    ).bind(orderId, `status.${newStatus}`, Date.now()),
    
    // Return updated order
    env.DB.prepare('SELECT * FROM orders WHERE id = ?').bind(orderId),
  ]);
  
  return results[2].results[0];  // The SELECT result
}

For the full Cloudflare Workers runtime patterns beyond D1, the Cloudflare Workers guide covers KV, R2, Queues, and Durable Objects. For the Drizzle ORM patterns that also apply to D1’s SQLite dialect, the Drizzle ORM guide covers schema definition and relational query patterns. The Claude Skills 360 bundle includes Cloudflare D1 skill sets covering schema migrations, Drizzle integration, and edge-optimized query patterns. Start with the free tier to try D1 Worker 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