Drizzle on D1
D1 uses a native Worker binding, so it needs neither a connection string nor Hyperdrive. For external Postgres/MySQL, use Worker + Drizzle.
Install
Section titled “Install”pnpm add drizzle-orm@1.0.0-rc.5-ab785fc @effect/sql-d1pnpm add -D drizzle-kit@1.0.0-rc.5-ab785fcThese versions match Alchemy’s current Effect-compatible Drizzle integration.
Define the schema
Section titled “Define the schema”import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
export const users = sqliteTable("users", { id: integer("id").primaryKey({ autoIncrement: true }), name: text("name").notNull(),});Configure generation
Section titled “Configure generation”import { defineConfig } from "drizzle-kit";
export default defineConfig({ dialect: "sqlite", schema: "./src/schema.ts", out: "./drizzle",});Generate and commit SQL
Section titled “Generate and commit SQL”pnpm exec drizzle-kit generategit diff -- drizzlegit add src/schema.ts drizzle.config.ts drizzlegit commit -m "Create users table"Generate migrations when the schema changes; CI deploys these committed files.
Wire the schema into the database
Section titled “Wire the schema into the database”import * as Cloudflare from "alchemy/Cloudflare";
export const Database = Cloudflare.D1.Database("Database", { migrations: "./drizzle",});D1 applies pending SQL during deployment. D1 migrations covers database-specific behavior; Drizzle migrations covers file generation.
Query from the Worker with Drizzle.D1
Section titled “Query from the Worker with 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 { 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 binding = yield* Cloudflare.D1.QueryDatabase(database); const db = yield* Drizzle.D1(binding);
return { fetch: Effect.gen(function* () { const rows = yield* db.select().from(users).pipe(Effect.orDie); return yield* HttpServerResponse.json({ users: rows }); }), }; }).pipe(Effect.provide(Cloudflare.D1.QueryDatabaseBinding)),) {}Define the stack
Section titled “Define the stack”import * as Alchemy from "alchemy";import * as Cloudflare from "alchemy/Cloudflare";import * as Effect from "effect/Effect";import Api from "./src/Api.ts";
export default Alchemy.Stack( "D1Drizzle", { providers: Cloudflare.providers(), state: Alchemy.localState() }, Effect.gen(function* () { const api = yield* Api; return { url: api.url }; }),);Deploy
Section titled “Deploy”pnpm alchemy deploycurl "$WORKER_URL"# {"users":[]}Set WORKER_URL to the deployed URL; keep .alchemy for later deployments and cleanup.
Local dev
Section titled “Local dev”pnpm alchemy devThe same Worker and binding use local D1 during development.
Queries and transactions
Section titled “Queries and transactions”Drizzle on D1 covers relational queries; Effect SQL on D1 covers tagged-template SQL. D1 uses batch operations rather than interactive transactions or streaming queries.
Where next
Section titled “Where next”See SQL databases, the D1 resource, or the existing D1 example.