DB module

Drizzle ORM and Cloudflare D1 schema, migrations, and access patterns.

DB module

The database uses Drizzle ORM with Cloudflare D1 (SQLite). D1 is configured via bindings in wrangler.jsonc, not environment variables (see Env for project env overview). Table definitions live in two files: auth.schema.ts (Better Auth, script-generatable) and app.schema.ts (application tables). Both are merged in schema.ts into a single schema for Drizzle and migrations.

Directory structure

src/db/
├── index.ts       # getDb() using env.DB (cloudflare:workers), re-exports schema
├── schema.ts      # Merges auth.schema + app.schema, exports schema and re-exports tables
├── auth.schema.ts # Source-aligned auth tables: user, session, account, verification + relations
├── app.schema.ts  # Source business tables
├── types.ts       # User, ApiKey, business row types ($inferSelect from tables)
└── migrations/    # Drizzle-generated SQL and meta

Configuration

  • wrangler.jsonc
    • d1_databases: binding: "DB", database_id, database_name, migrations_dir: "./src/db/migrations".
  • At runtime the D1 instance is available as env.DB (Cloudflare Workers). The project uses import { env } from "cloudflare:workers" in getDb(), so no argument is passed.

Core API

  • getDb()

    • Returns drizzle(env.DB, { schema }) for all server-side DB access. The schema is built in schema.ts from auth.schema and app.schema. Called without arguments (uses env from cloudflare:workers).
  • Exports (index.ts)

    • Re-exports from schema.ts (which re-exports auth.schema and app.schema). Import tables from @/db or @/db/auth.schema / @/db/app.schema.
  • types.ts

    • User, ApiKey, UserFiles — inferred from table $inferSelect. Use for API responses and admin tables.

Schema design

  • auth.schema.ts

    • Auth tables aligned to /Users/rusk/developer/project-template/src/db/schema.ts: user, session, account, verification, plus Drizzle relations. Do not add target-only compatibility fields such as normalizedEmail or customerId.
  • app.schema.ts

    • Source business tables such as api_key, order, subscription, credit, RBAC, content, and keyword tables. Target-only compatibility tables are not kept.
  • schema.ts

    • Re-exports all tables from auth.schema and app.schema; exports schema = { ...authSchema, ...appSchema }. Used by getDb() and drizzle-kit as the single schema entry point.

Migrations

  • Generate: pnpm db:generate (uses drizzle.config.ts or local config).
  • Apply:
    • Local D1: pnpm db:migrate:local.
    • Remote D1: pnpm db:migrate:remote.
  • Push schema (dev only): pnpm db:push.
  • drizzle-kit:
    • Remote: drizzle.config.ts (D1 HTTP + account credentials).
    • Local: drizzle.config.local.ts (local SQLite path, e.g. for Studio).

Consumers

  • Auth (src/auth/auth.ts): drizzleAdapter(getDb(), { provider: 'sqlite' }); Better Auth reads/writes user, session, account, and verification. User-facing API keys use the source api_key business table, not Better Auth's apikey table.
  • Other server logic: import { getDb } from '@/db' and call getDb(), then run queries. Import tables from @/db or @/db/auth.schema / @/db/app.schema as needed.

Notes

  • D1 is SQLite; use Drizzle’s SQLite dialect (sqliteTable, text, integer, etc.).
  • All DB access must run in the Worker or server; getDb depends on D1Database and is not for use in the browser.
xs