D1
D1 is a SQL database Cloudflare runs for you, built on SQLite. You write ordinary SQL against tables you define, and there’s no server to size: you pay for the rows you read and write and the data you store.
One primary, near its users
Section titled “One primary, near its users”A D1 database has one primary copy, in one location. Every write goes to it, in order, so there’s no conflict to resolve. Workers in any city reach it through a binding.
A database holds up to 10 GB. That’s a lot for one app’s data, and a hint about how D1 is meant to be used: many small databases, one per app, per tenant or per user, rather than one huge one.
Create a database
Section titled “Create a database”import * as Cloudflare from "@alchemy.run/cloudflare";
export const Database = Cloudflare.D1.Database("Database", { migrations: "./migrations" });The primary is placed near the first requests. Set primaryLocationHint, like "weur", to choose. To keep its data inside a legal boundary, set jurisdiction.
Apply migrations on deploy
Section titled “Apply migrations on deploy”migrations points at a directory of .sql files. Each deploy applies the ones that haven’t run yet, in the order of their numeric prefix, 0001_, 0002_:
CREATE TABLE users (id INTEGER PRIMARY KEY, email TEXT NOT NULL UNIQUE);Applied files are recorded in an __alchemy_migrations table. A database already migrated with wrangler or drizzle-kit is picked up where it left off. To generate migrations from a schema instead of writing them, see Drizzle on D1.
Query from a Worker
Section titled “Query from a Worker”Bind the database with D1.QueryDatabase, and prepare statements against it:
export default Cloudflare.Worker( "Worker", { main: import.meta.url }, Effect.gen(function* () { const db = yield* Cloudflare.D1.QueryDatabase(Database); return { fetch: Effect.gen(function* () { const users = yield* db.prepare("SELECT * FROM users WHERE id = ?").bind(1).all(); return yield* HttpServerResponse.json(users); }).pipe(Effect.orDie), }; }).pipe(Effect.provide(Cloudflare.D1.QueryDatabaseBinding)),);prepare and bind only build the statement. all, first and run send it, and return Effects. batch sends several statements in one round trip, applied together or not at all.
Write SQL as Effect SQL
Section titled “Write SQL as Effect SQL”For SQL written as a template, with values parameterized for you and typed errors, wrap the binding in an Effect SQL client:
const d1 = yield* Cloudflare.D1.QueryDatabase(Database);const sql = yield* SQL.D1(d1);
const users = yield* sql`SELECT * FROM users WHERE id = ${id}`;SQL comes from @alchemy.run/cloudflare/D1/SQL. The client implements Effect’s SqlClient, so code written against SqlClient.SqlClient runs on D1 unchanged. See Effect SQL on D1.
Read close to users
Section titled “Read close to users”With read replication on, D1 keeps read-only copies of the database in other regions, and serves reads from the nearest one:
export const Database = Cloudflare.D1.Database("Database", { migrations: "./migrations", readReplication: { mode: "auto" },});A replica can be slightly behind the primary. To read your own writes, use Cloudflare’s sessions API on the native database, which the client’s raw escape hatch gives you: a session carries a bookmark from each write, and later reads in the session wait until their replica has caught up to it.
When to use something else
Section titled “When to use something else”Use D1 for relational data owned by one app or one tenant. For data that’s read everywhere and rarely written, KV is faster. For state that belongs to one entity and must be consistent per request, a Durable Object’s own SQLite fits better. For Postgres or MySQL you already run, or need, connect through Hyperdrive.