claude-start-cf

D1 Database

Cloudflare D1 is a serverless SQLite database that runs at the edge. Accessed via getDB() inside server functions.

Table schema

PRAGMA table_info('notes')

The notes table is created automatically on first load via CREATE TABLE IF NOT EXISTS. Here's the live schema from D1.

ColumnTypePKNOT NULL
idINTEGERyes
titleTEXTyes
bodyTEXTyes
created_atTEXTyes

0 rows in the database.

Create a note

INSERT INTO notes (title, body) VALUES (?, ?)

Uses a POST server function with input validation. The data is bound with parameterized queries to prevent SQL injection.

const createNote = createServerFn({ method: 'POST' })
  .inputValidator((data: { title: string; body: string }) => {
    if (!data.title.trim()) throw new Error('Title is required')
    return data
  })
  .handler(async ({ data }) => {
    const db = getDB()
    const result = await db
      .prepare('INSERT INTO notes (title, body) VALUES (?, ?)')
      .bind(data.title, data.body)
      .run()
    return { id: result.meta.last_row_id }
  })

Notes from D1

SELECT * FROM notes ORDER BY id DESC

Loaded via the route loader on first render (SSR), refreshed client-side via router.invalidate(). Each row has a delete button that calls a POST server function.

No notes yet. Create one above.

Accessing D1 via getDB()

src/lib/env.ts

The env helper wraps the cloudflare:workers import and provides typed access to all bindings — D1, R2, KV, etc. Import getDB() in any server function.

// src/lib/env.ts
import { env } from 'cloudflare:workers'

export function getEnv(): Env {
  return env as Env
}

export function getDB(): D1Database {
  return getEnv().DB
}
// In any server function
import { getDB } from '~/lib/env'

const myServerFn = createServerFn().handler(async () => {
  const db = getDB()
  const { results } = await db
    .prepare('SELECT * FROM notes')
    .all()
  return results
})

The Env type is auto-generated by wrangler types into worker-configuration.d.ts. Add bindings to wrangler.jsonc, run wrangler types, and the types update.

Setup checklist

wrangler.jsonc + wrangler types

Steps to add D1 to a new project.

  1. Create the database
    wrangler d1 create my-db
  2. Add the binding to wrangler.jsonc
    "d1_databases": [{
      "binding": "DB",
      "database_name": "my-db",
      "database_id": "..."
    }]
  3. Regenerate types
    wrangler types
  4. Use getDB() in server functions
    const db = getDB()
    await db.prepare('SELECT 1').run()

Migrations: For production apps, use wrangler d1 migrations create and wrangler d1 migrations apply instead of CREATE TABLE IF NOT EXISTS. The demo uses the simple approach for clarity.