Use Cloudflare D1 with DaloyJS
Cloudflare D1 is a serverless SQLite-compatible database built into Cloudflare Workers. A Worker reaches it through a binding (no network driver, no auth token in your code, no connection to manage), making it the lowest-friction database for the Cloudflare Workers adapter. If you already have Postgres or MySQL, use Hyperdrive instead.
1. Provision via Wrangler
Add the returned binding to your wrangler.jsonc:
2. Type the binding
Generate types from your Wrangler config instead of writing an Env interface by hand:
This writes worker-configuration.d.ts with your bindings, so env.DB from cloudflare:workers is typed as D1Database without a cast. Rerun it whenever you change wrangler.jsonc, and do not edit the generated file.
3. Decorate the app with the binding
D1 bindings live on env, not process.env. Import env from cloudflare:workers and decorate the app with the binding once, before you register routes. Do not decorate inside the Worker's fetch handler: decorate() throws on the second request, and a decoration added after the routes were registered never reaches them.
- 01import { env } from "cloudflare:workers"env.DB is the D1 binding
- 02app.decorate('db', env.DB)once, before the routes
- 03toFetchHandler(app)route handler reads state.db
- 04state.db.prepare(...).all()query D1, no network driver
Keep the declare module block in the Worker module itself, not in a separate .d.ts file. Declaration files are exempt from type-checking when skipLibCheck is on (the scaffolded default), so a mistake inside a .d.ts fails silently and state.db degrades to any. Bind request values with .bind() (prepare("select * from todos where id = ?").bind(id)), never by string concatenation.
Read replication and the Sessions API
D1 can serve reads from replicas in other regions. Turn on read replication in the dashboard (database Settings) or the REST API, then run queries through a session so a client always reads data at least as new as its last write. A session starts from a constraint ("first-unconstrained", the default, or "first-primary") or from a bookmark returned by an earlier session. Cloudflare's example passes the bookmark back and forth in an x-d1-bookmark header. Check Cloudflare's read replication docs for the current release status.
A session is a per-request value, so set it in a hook instead of decorate():
Register the hook before the routes. Use "first-primary" for routes that must read the latest committed data. Browser clients on another origin need x-d1-bookmark in the cors() exposedHeaders and allowedHeaders options.
Migrations
Run one-off SQL with wrangler d1 execute, against the local copy used by wrangler dev or the real database:
Backups: Time Travel and export
- Time Travel restores a database to any minute in the retention window (30 days on Workers Paid, 7 days on Free):
pnpm wrangler d1 time-travel info my-app-dbandpnpm wrangler d1 time-travel restore my-app-db --timestamp=<unix-or-rfc3339>. A restore overwrites the database in place and cancels in-flight queries. - Export a SQL dump for longer retention or local copies:
pnpm wrangler d1 export my-app-db --remote --output=./backup.sql. See import and export.
With Drizzle ORM
Decorate db before the routes, as above.
With Prisma
Prisma supports D1 through the D1 driver adapter (@prisma/adapter-d1). With Prisma 7, generate the client with the prisma-client generator and an output path, construct new PrismaD1(env.DB) from the imported env, pass it as adapter, and decorate the client before the routes, like the binding above. Prisma Migrate does not apply migrations to D1 directly; generate SQL with prisma migrate diff and apply it with wrangler d1 migrations. See Prisma.
Limitations to know
- The binding exists only inside Workers. Outside Workers (Node, Lambda, CI scripts) D1 is reachable only through Cloudflare's REST API, which is meant for administration and tooling rather than serving application traffic. Node can also run your Worker locally through
wrangler dev. - Local development uses
wrangler devwith a local SQLite file. Behavior is close but not identical to production. - For multi-runtime portability, prefer Turso (libSQL), or Postgres through Hyperdrive on Workers and directly elsewhere.
See also Turso, Cloudflare Hyperdrive, Neon, and the database hosting overview.