1---
2title: D1
3description: SQLite schemas and Effect-native Drizzle queries over a Cloudflare D1 binding.
4---
5
6import { Code, Tabs, TabItem } from "@astrojs/starlight/components";
7
8export const deps = `drizzle-orm@1.0.0-rc.5-ab785fc @effect/sql-d1`;
9export const devDeps = `drizzle-kit@1.0.0-rc.5-ab785fc`;
10
11`Drizzle.D1` provides typed, Effect-native queries over a Cloudflare D1 binding; unlike Postgres and MySQL, this driver is Cloudflare-specific. See [SQL databases](/sql/databases) to compare engines and [Cloudflare setup](/cloudflare/setup) for deployment choices.
12
13Install the toolchain — all optional peers of alchemy:
14
15<Tabs syncKey="pkgManager">
16 <TabItem label="bun" icon="bun">
17 <Code code={`bun add ${deps}\nbun add -d ${devDeps}`} lang="sh" />
18 </TabItem>
19 <TabItem label="npm" icon="npm">
20 <Code code={`npm install ${deps}\nnpm install -D ${devDeps}`} lang="sh" />
21 </TabItem>
22 <TabItem label="pnpm" icon="pnpm">
23 <Code code={`pnpm add ${deps}\npnpm add -D ${devDeps}`} lang="sh" />
24 </TabItem>
25 <TabItem label="yarn" icon="seti:yarn">
26 <Code code={`yarn add ${deps}\nyarn add -D ${devDeps}`} lang="sh" />
27 </TabItem>
28</Tabs>
29
30## Define the schema
31
32Drizzle schemas are plain TypeScript modules using the
33`sqlite-core` column builders:
34
35```typescript
36// src/schema.ts
37import { defineRelations } from "drizzle-orm";
38import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
39
40export const Users = sqliteTable("users", {
41 id: integer("id").primaryKey({ autoIncrement: true }),
42 email: text("email").notNull().unique(),
43 name: text("name").notNull(),
44});
45
46export const Posts = sqliteTable("posts", {
47 id: integer("id").primaryKey({ autoIncrement: true }),
48 userId: integer("user_id")
49 .notNull()
50 .references(() => Users.id, { onDelete: "cascade" }),
51 title: text("title").notNull(),
52});
53
54export const relations = defineRelations({ Users, Posts }, (t) => ({
55 Users: { posts: t.many.Posts() },
56 Posts: {
57 user: t.one.Users({ from: t.Posts.userId, to: t.Users.id }),
58 },
59}));
60```
61
62## Generate and review migrations
63
64```typescript
65// drizzle.config.ts
66import { defineConfig } from "drizzle-kit";
67
68export default defineConfig({
69 schema: "./src/schema.ts",
70 out: "./migrations",
71 dialect: "sqlite",
72});
73```
74
75```sh
76bunx drizzle-kit generate
77git add src/schema.ts drizzle.config.ts migrations
78git diff --cached
79git commit -m "Add database migration"
80```
81
82Generate with each schema change and review the schema, SQL, and snapshots together before committing. [Migrations](/sql/drizzle/migrations) covers application ownership; the optional [`Drizzle.Schema` resource](/providers/drizzle/schema) is not required.
83
84## Declare the database
85
86```typescript
87// src/db.ts
88import * as Cloudflare from "alchemy/Cloudflare";
89
90export const Database = Cloudflare.D1.Database("app-db", {
91 migrations: "./migrations",
92});
93```
94
95With `Cloudflare.providers()` registered, the resource applies committed files during deploy without generating SQL. See the [Cloudflare D1 walkthrough](/cloudflare/data/d1-drizzle) for the full deployment; keep an existing migration runner unless you deliberately choose Alchemy-managed application.
96
97## Connect in a Worker
98
99Bind the database with `Cloudflare.D1.QueryDatabase` and hand the
100client to `Drizzle.D1`:
101
102```typescript
103// src/api.ts
104import * as Cloudflare from "alchemy/Cloudflare";
105import * as Drizzle from "alchemy/Drizzle";
106import * as Effect from "effect/Effect";
107import * as HttpServerResponse from "effect/http/HttpServerResponse";
108import { Database } from "./db.ts";
109import { relations, Users } from "./schema.ts";
110
111export default class Api extends Cloudflare.Worker<Api>()(
112 "Api",
113 { main: import.meta.url },
114 Effect.gen(function* () {
115 const database = yield* Database;
116 const d1 = yield* Cloudflare.D1.QueryDatabase(database);
117 const db = yield* Drizzle.D1(d1, { relations });
118
119 return {
120 fetch: Effect.gen(function* () {
121 const users = yield* db.select().from(Users);
122 return yield* HttpServerResponse.json({ users });
123 }),
124 };
125 }).pipe(Effect.provide(Cloudflare.D1.QueryDatabaseBinding)),
126) {}
127```
128
129Client construction does not query D1; the first query builds the client and reuses it within the event's [scope](/sql/effect-sql/lifecycle). Resource provisioning and migration application still run during deployment.
130
131## Queries are Effects
132
133Every builder yields directly, with `SqlError` in the typed error
134channel:
135
136```typescript
137import { eq } from "drizzle-orm";
138
139const [created] = yield* db
140 .insert(Users)
141 .values({ name, email })
142 .returning();
143
144const [removed] = yield* db
145 .delete(Users)
146 .where(eq(Users.id, id))
147 .returning();
148```
149
150Because `relations` was passed to `Drizzle.D1`, the typed
151`db.query.*` API is available:
152
153```typescript
154const user = yield* db.query.Users.findFirst({
155 where: { id },
156 with: { posts: true },
157});
158```
159
160D1 has no session transactions — for atomic multi-statement writes
161use the binding's `batch` (see
162[Batches instead of transactions](/sql/effect-sql/d1#batches-instead-of-transactions)).
163
164## Local dev
165
166`alchemy dev` runs the same Worker against a local D1 database via
167miniflare — `Drizzle.D1` works unchanged because it resolves
168whatever `D1Database` binding the runtime provides.
169
170## Where next
171
172The [Cloudflare D1 walkthrough](/cloudflare/data/d1-drizzle) owns deployment, while [Effect SQL: D1](/sql/effect-sql/d1) covers tagged-template queries over the same binding. The [D1 example](https://github.com/alchemy-run/alchemy/tree/main/examples/cloudflare-d1-drizzle) demonstrates schema and query code but uses the optional schema resource rather than this guide's generation workflow.