Skip to content

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.

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

These versions match Alchemy’s current Effect-compatible Drizzle integration.

src/schema.ts
import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
export const users = sqliteTable("users", {
id: integer("id").primaryKey({ autoIncrement: true }),
name: text("name").notNull(),
});
drizzle.config.ts
import { defineConfig } from "drizzle-kit";
export default defineConfig({
dialect: "sqlite",
schema: "./src/schema.ts",
out: "./drizzle",
});
Terminal window
pnpm exec drizzle-kit generate
git diff -- drizzle
git add src/schema.ts drizzle.config.ts drizzle
git commit -m "Create users table"

Generate migrations when the schema changes; CI deploys these committed files.

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

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 { 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)),
) {}
alchemy.run.ts
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 };
}),
);
Terminal window
pnpm alchemy deploy
curl "$WORKER_URL"
# {"users":[]}

Set WORKER_URL to the deployed URL; keep .alchemy for later deployments and cleanup.

Terminal window
pnpm alchemy dev

The same Worker and binding use local D1 during development.

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.

See SQL databases, the D1 resource, or the existing D1 example.