Skip to content

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:

Terminal window
bun add drizzle-orm@1.0.0-rc.5-ab785fc @effect/sql-d1
bun add -d drizzle-kit@1.0.0-rc.5-ab785fc

Drizzle schemas are plain TypeScript modules using the sqlite-core column builders:

src/schema.ts
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 }),
},
}));
drizzle.config.ts
import { defineConfig } from "drizzle-kit";
export default defineConfig({
schema: "./src/schema.ts",
out: "./migrations",
dialect: "sqlite",
});
Terminal window
bunx drizzle-kit generate
git add src/schema.ts drizzle.config.ts migrations
git diff --cached
git 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.

src/db.ts
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.

Bind the database with Cloudflare.D1.QueryDatabase and hand the client to Drizzle.D1:

src/api.ts
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.

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).

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.

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.