D1
Drizzle.D1 provides typed, Effect-native queries over a Cloudflare D1 binding; unlike Postgres and MySQL, this driver is Cloudflare-specific. See SQL databases to compare engines and Cloudflare setup for deployment choices.
Install the toolchain — all optional peers of alchemy:
bun add drizzle-orm@1.0.0-rc.5-ab785fc @effect/sql-d1bun add -d drizzle-kit@1.0.0-rc.5-ab785fcnpm install drizzle-orm@1.0.0-rc.5-ab785fc @effect/sql-d1npm install -D drizzle-kit@1.0.0-rc.5-ab785fcpnpm add drizzle-orm@1.0.0-rc.5-ab785fc @effect/sql-d1pnpm add -D drizzle-kit@1.0.0-rc.5-ab785fcyarn add drizzle-orm@1.0.0-rc.5-ab785fc @effect/sql-d1yarn add -D drizzle-kit@1.0.0-rc.5-ab785fcDefine the schema
Section titled “Define the schema”Drizzle schemas are plain TypeScript modules using the
sqlite-core column builders:
import { defineRelations } from "drizzle-orm";import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
export const Users = sqliteTable("users", { id: integer("id").primaryKey({ autoIncrement: true }), email: text("email").notNull().unique(), name: text("name").notNull(),});
export const Posts = sqliteTable("posts", { id: integer("id").primaryKey({ autoIncrement: true }), userId: integer("user_id") .notNull() .references(() => Users.id, { onDelete: "cascade" }), title: text("title").notNull(),});
export const relations = defineRelations({ Users, Posts }, (t) => ({ Users: { posts: t.many.Posts() }, Posts: { user: t.one.Users({ from: t.Posts.userId, to: t.Users.id }), },}));Generate and review migrations
Section titled “Generate and review migrations”import { defineConfig } from "drizzle-kit";
export default defineConfig({ schema: "./src/schema.ts", out: "./migrations", dialect: "sqlite",});bunx drizzle-kit generategit add src/schema.ts drizzle.config.ts migrationsgit diff --cachedgit commit -m "Add database migration"Generate with each schema change and review the schema, SQL, and snapshots together before committing. Migrations covers application ownership; the optional Drizzle.Schema resource is not required.
Declare the database
Section titled “Declare the database”import * as Cloudflare from "alchemy/Cloudflare";
export const Database = Cloudflare.D1.Database("app-db", { migrations: "./migrations",});With Cloudflare.providers() registered, the resource applies committed files during deploy without generating SQL. See the Cloudflare D1 walkthrough for the full deployment; keep an existing migration runner unless you deliberately choose Alchemy-managed application.
Connect in a Worker
Section titled “Connect in a Worker”Bind the database with Cloudflare.D1.QueryDatabase and hand the
client to Drizzle.D1:
import * as Cloudflare from "alchemy/Cloudflare";import * as Drizzle from "alchemy/Drizzle";import * as Effect from "effect/Effect";import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse";import { Database } from "./db.ts";import { relations, Users } from "./schema.ts";
export default class Api extends Cloudflare.Worker<Api>()( "Api", { main: import.meta.url }, Effect.gen(function* () { const database = yield* Database; const d1 = yield* Cloudflare.D1.QueryDatabase(database); const db = yield* Drizzle.D1(d1, { relations });
return { fetch: Effect.gen(function* () { const users = yield* db.select().from(Users); return yield* HttpServerResponse.json({ users }); }), }; }).pipe(Effect.provide(Cloudflare.D1.QueryDatabaseBinding)),) {}Client construction does not query D1; the first query builds the client and reuses it within the event’s scope. Resource provisioning and migration application still run during deployment.
Queries are Effects
Section titled “Queries are Effects”Every builder yields directly, with SqlError in the typed error
channel:
import { eq } from "drizzle-orm";
const [created] = yield* db .insert(Users) .values({ name, email }) .returning();
const [removed] = yield* db .delete(Users) .where(eq(Users.id, id)) .returning();Because relations was passed to Drizzle.D1, the typed
db.query.* API is available:
const user = yield* db.query.Users.findFirst({ where: { id }, with: { posts: true },});D1 has no session transactions — for atomic multi-statement writes
use the binding’s batch (see
Batches instead of transactions).
Local dev
Section titled “Local dev”alchemy dev runs the same Worker against a local D1 database via
miniflare — Drizzle.D1 works unchanged because it resolves
whatever D1Database binding the runtime provides.
Where next
Section titled “Where next”The Cloudflare D1 walkthrough owns deployment, while Effect SQL: D1 covers tagged-template queries over the same binding. The D1 example demonstrates schema and query code but uses the optional schema resource rather than this guide’s generation workflow.