Cloudflare Workers and D1: SQL Database API
Cloudflare D1 is a managed, serverless SQL database that uses SQLite's SQL semantics. A Worker reaches it through a binding: a name you give in the Wrangler configuration, which then appears in your code as env.DB. There is no connection string to manage and no driver to set up, so a Worker with D1 is a short path to an API with relational data.
This template shows that API together with the three things a real project adds soon after the first query: migration files that version the schema, the binding in the Wrangler configuration, and read replicas for read-heavy traffic.
Scroll sideways to see the whole diagram
Start from this diagram and edit it on your own board.
By continuing, you agree to the Terms of Service and Privacy Policy, including sending images of your strokes, diagram labels and similar data to providers in the United States (Cloudflare, Inc. and TypeSafe AI, Inc.) for AI conversion.
What each part does
- Client
- A browser, an app or another service that calls the API over HTTPS. If read replication is on, it also carries a bookmark from one request to the next, so that it can read what it just wrote.
- Worker
- Your API code. It builds each query with env.DB.prepare(sql).bind(values), where ? placeholders keep values out of the SQL text, and runs it with methods such as run(), all() and first(). env.DB.batch() sends several statements together; they run in order, and if one fails the whole sequence is rolled back.
- Wrangler config
- Declares the binding in a d1_databases entry with a binding name, a database_name and a database_id. It can also set the folder of the migration files (migrations_dir) and, for a separate preview database, a preview_database_id.
- D1 primary
- The database instance that accepts writes. Every write query is forwarded to it, whether or not replicas exist. It is also where a session can be told to start (first-primary) when the freshest data matters.
- D1 read replica
- A read-only copy that Cloudflare creates for you in supported regions once read replication is switched on for the database. It serves reads closer to the user. A session that carries a bookmark is only served by a replica that has caught up to it.
- Migration files
- Numbered .sql files in the migrations folder. wrangler d1 migrations create adds one, migrations list shows the ones not yet applied, and migrations apply runs them. D1 keeps a record of what ran in a d1_migrations table.
How a request flows
- The client sends an HTTPS request to the Worker.
- The Worker prepares a statement on its D1 binding and binds the request's values to it. A write goes to the primary, and related writes can be sent together with batch().
- For reads, the Worker opens a session with env.DB.withSession(bookmark). The first query of a session can be served by the primary or any replica, and later queries in the session never see an older version of the database than the one before.
- When the session carries a bookmark, a replica answers only once it is at least as up to date as that bookmark, so the read reflects the version the bookmark points to or a newer one.
- The Worker returns its JSON response and includes the session's latest bookmark (session.getBookmark()), for example in a response header. The client sends it back on its next request.
- Separately from requests, a schema change is a new migration file. wrangler d1 migrations apply runs the files that have not run yet against the database.
When to use it
- A JSON API for relational data, such as users, orders or posts, where you want SQL and joins rather than key-value lookups.
- Read-heavy sites, such as a catalog or a content tool, that benefit from reading near the user.
- Per-project databases for internal tools, with the schema kept in migration files next to the code.
Common variations
Turn on read replication
Switch it on per database in the dashboard (D1, your database, Settings, Enable Read Replication) and use withSession() in the Worker. Replicas are created automatically and add no storage or compute charge; billing still counts rows read and written. Pass first-primary to withSession() when a request must start from the latest data.
Use an ORM with nested migration folders
Some ORMs, such as Drizzle, write each migration into its own folder. Set migrations_pattern next to migrations_dir in the binding (for example migrations/*/migration.sql) so that wrangler d1 migrations apply finds them.
Keep a preview database apart from production
Give the binding a preview_database_id for local and preview runs. When you run migrations, use the database name rather than the binding name: the binding name can change, the database name cannot.
Recover from a bad change
D1 Time Travel can restore a database to any minute within the last 30 days, which helps when a migration or a query has damaged data.
Make it yours
Rename the binding and the database after your project, then replace the example tables in the first migration file with your own.
Opens this diagram as a board you can edit.
By continuing, you agree to the Terms of Service and Privacy Policy, including sending images of your strokes, diagram labels and similar data to providers in the United States (Cloudflare, Inc. and TypeSafe AI, Inc.) for AI conversion.