D1
D1 is Cloudflare’s serverless SQLite database. There’s nothing to provision or scale — you get a SQL database addressed by name, with reads served close to your users when read replication is enabled.
Reach for D1 when your app needs relational data without operating a database: user tables, content, app state queried with plain SQL. If you need Postgres or MySQL (or already have one), front it with Hyperdrive instead.
Create a database
Section titled “Create a database”A database is one resource declaration:
import * as Cloudflare from "alchemy/Cloudflare";
export const Database = Cloudflare.D1.Database("Database");Yield it inside your Stack like any other resource:
import * as Alchemy from "alchemy";import * as Cloudflare from "alchemy/Cloudflare";import * as Effect from "effect/Effect";import { Database } from "./src/database.ts";
export default Alchemy.Stack( "MyApp", { providers: Cloudflare.providers(), state: Cloudflare.state(), }, Effect.gen(function* () { const db = yield* Database;
return { databaseName: db.databaseName }; }),);Migrations
Section titled “Migrations”Point migrationsDir at a folder of .sql files. Files are sorted
by numeric prefix (0001_, 0002_, …) and applied in order as part
of every deploy; already-applied migrations are skipped:
const db = yield* Cloudflare.D1.Database("my-db", { migrationsDir: "./migrations",});The tracking table is wrangler-compatible, and you can point at a custom one (e.g. for Drizzle-generated migrations):
const db = yield* Cloudflare.D1.Database("my-db", { migrationsDir: "./migrations", migrationsTable: "drizzle_migrations",});The migration files themselves don’t have to be hand-written — Drizzle.Schema can generate them from a Drizzle schema on deploy.
Query from a Worker
Section titled “Query from a Worker”Bind the database into a Worker with Cloudflare.D1.QueryDatabase
and provide the QueryDatabaseBinding layer. The client exposes
prepare, exec, batch, and raw; prepared statements execute
with all, first, or run:
import * as Cloudflare from "alchemy/Cloudflare";import * as Effect from "effect/Effect";import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse";import { Database } from "./database.ts";
export default Cloudflare.Worker( "Worker", { main: import.meta.url }, Effect.gen(function* () { // init: register the binding, get a typed client const db = yield* Cloudflare.D1.QueryDatabase(Database);
return { fetch: Effect.gen(function* () { // runtime: run queries const results = yield* db .prepare("SELECT * FROM users WHERE id = ?") .bind(1) .all(); return yield* HttpServerResponse.json(results); }), }; }).pipe(Effect.provide(Cloudflare.D1.QueryDatabaseBinding)),);prepare and bind are synchronous plan builders — only the
executors (all, first, run, raw) round-trip to the database,
and those return Effects. For libraries that want the native
D1Database object (e.g. Better Auth), use the client’s raw
escape hatch.
Where next
Section titled “Where next”Guides:
- Add Drizzle ORM — typed schemas and
generated migrations (the D1 resource’s
migrationsTableprop accepts Drizzle’s).
Related:
- Workers — where your queries run.
- Hyperdrive — for external Postgres/MySQL instead of SQLite.
Reference: