Create FasterCreate Faster

Cloudflare D1

Cloudflare D1 is a managed SQLite database running at the edge, accessed through the Workers binding API.

→ Cloudflare D1 Documentation

What create-faster adds

D1 requires --deployment cloudflare — the DB binding is injected by the Workers runtime, so there is no connection string. create-faster wires this binding end-to-end across every generated file.

wrangler.jsonc — D1 binding declaration:

"d1_databases": [
  {
    "binding": "DB",
    "database_name": "{appName}-db",
    "database_id": "REPLACE_WITH_D1_DATABASE_ID",
    "migrations_dir": "drizzle"
  }
]

Replace REPLACE_WITH_D1_DATABASE_ID with the id returned by wrangler d1 create.

src/lib/db/index.ts — factory, not singleton:

D1 is injected per request, not available at module load time. The generated client is a createDb factory that accepts the binding:

import { drizzle } from 'drizzle-orm/d1';
import * as schema from './schema';

export function createDb(d1: D1Database) {
  return drizzle(d1, { schema });
}
export type Database = ReturnType<typeof createDb>;
  • Single repo: src/lib/db/index.ts
  • Turborepo: packages/db/src/index.ts

src/lib/env.ts (Next.js only) — getEnv seam:

For Next.js apps, a getEnv helper retrieves the binding from the OpenNext Cloudflare context:

import 'server-only';
import { getCloudflareContext } from '@opennextjs/cloudflare';

export type Env = { DB: D1Database };

export async function getEnv(): Promise<Env> {
  const { env } = await getCloudflareContext({ async: true });
  return { DB: env.DB };
}

Call getEnv() in Server Components or Route Handlers, then pass env.DB to createDb:

const env = await getEnv();
const db = createDb(env.DB);

src/lib/server.ts (Next.js only) — per-request getDb/getAuth:

When the app also selects an ORM (and optionally Better Auth), create-faster generates an app-side accessor that composes the binding into ready-to-use per-request instances:

import 'server-only';
import { createDb } from '@/lib/db';
import { createAuth } from '@/lib/auth/auth';
import { getEnv } from '@/lib/env';

export async function getDb() {
  return createDb((await getEnv()).DB);
}

export async function getAuth() {
  return createAuth(await getDb());
}

Better Auth & tRPC on D1

Because D1 has no module-level db singleton, Better Auth and tRPC are wired per request instead of importing a singleton:

  • Better Auth (auth.ts) exports a createAuth(db: Database) factory (with the shared config) instead of export const auth. The auth route handler resolves it per request via await getAuth().
  • tRPC (init.ts / packages/api/src/trpc.ts) takes the db in its context and builds auth = createAuth(db) inside createTRPCContext. The Server Component caller and the route handler supply it with db: await getDb().

This is fully supported in both single-repo and turborepo modes:

bunx create-faster myapp \
  --app web:nextjs:trpc,better-auth \
  --database d1 \
  --orm drizzle \
  --deployment cloudflare

drizzle.config.ts — sqlite dialect with d1-http prod branch:

The config uses the sqlite dialect locally (pointing at the miniflare D1 file) and switches to the d1-http driver in production:

const isProduction = process.env.NODE_ENV === 'production';

export default defineConfig({
  dialect: 'sqlite',
  ...(isProduction
    ? {
        driver: 'd1-http',
        dbCredentials: {
          accountId: process.env.CLOUDFLARE_ACCOUNT_ID,
          databaseId: process.env.CLOUDFLARE_D1_DATABASE_ID,
          token: process.env.CLOUDFLARE_API_TOKEN,
        },
      }
    : { dbCredentials: { url: getLocalD1DB() ?? '' } }),
});

Schema uses sqliteTable from drizzle-orm/sqlite-core — identical to the SQLite option. Schemas carry over unchanged between local and D1.

Environment Variables

Generated in .env.example (turborepo: packages/db/.env.example and apps/{appName}/.env.example for each app with D1; single repo: root .env.example):

CLOUDFLARE_ACCOUNT_ID=""          # Cloudflare account id (drizzle-kit d1-http, prod migrations)
CLOUDFLARE_D1_DATABASE_ID=""      # D1 database id from `wrangler d1 create`
CLOUDFLARE_API_TOKEN=""           # Cloudflare API token with D1 edit permission

These are only needed for db:migrate:remote (production migrations via d1-http). Local development uses the miniflare SQLite file automatically.

Workflow Scripts

ScriptPurpose
db:generateGenerate migration SQL from schema changes (drizzle-kit generate)
db:migrate / db:migrate:localApply migrations to the local D1
db:migrate:remoteApply migrations to the production D1 database
local-setupFirst-time local setup: migrate + seed

Single repo — everything is co-located at the project root, so migrate runs against the root wrangler.jsonc and the local state at ./.wrangler.

Turborepo — the migration SQL lives in packages/db/drizzle, but the D1 binding lives in the consuming app's wrangler.jsonc. The migrate scripts (in packages/db) therefore point wrangler at that app's config and persist to a single shared state dir at the workspace root:

wrangler --config ../../apps/<app>/wrangler.jsonc d1 migrations apply DB --local --persist-to ../../.wrangler

The app's wrangler.jsonc sets migrations_dir to ../../packages/db/drizzle, and opennextjs-cloudflare preview runs with the same --persist-to ../../.wrangler — so migrate, preview, and drizzle.config.ts all read and write the one local database. Run turbo db:migrate (or bun run db:migrate at the root) to apply migrations across the workspace.

Local development

--persist-to <dir> stores the local miniflare D1 under <dir>/v3/d1/. The generated drizzle.config.ts auto-discovers the latest SQLite file in .wrangler/v3/d1/ (root .wrangler/v3/d1/ in turborepo), so db:push and db:studio work without any env var once migrations have been applied.

Manual smoke test after local-setup:

bun run db:migrate    # apply migrations to the local D1
bun run preview       # build + start the Worker with the local D1 binding

ORM constraint

D1 only works with Drizzle. --database d1 --orm prisma is rejected at validation time, and the interactive ORM prompt only offers compatible options.

bunx create-faster myapp \
  --app myapp:nextjs \
  --database d1 \
  --orm drizzle \
  --deployment cloudflare

On this page