1---
2title: Drizzle on D1
3description: Deploy committed SQLite migrations to D1 and query the database from a Worker with Drizzle.
4---
5
6D1 uses a native Worker binding, so it needs neither a connection string nor Hyperdrive. For external Postgres/MySQL, use [Worker + Drizzle](/cloudflare/data/drizzle).
7
8## Install
9
10```sh
11pnpm add drizzle-orm@1.0.0-rc.5-ab785fc @effect/sql-d1
12pnpm add -D drizzle-kit@1.0.0-rc.5-ab785fc
13```
14
15These versions match Alchemy's current Effect-compatible Drizzle integration.
16
17## Define the schema
18
19```typescript
20// src/schema.ts
21import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
22
23export const users = sqliteTable("users", {
24 id: integer("id").primaryKey({ autoIncrement: true }),
25 name: text("name").notNull(),
26});
27```
28
29## Configure generation
30
31```typescript
32// drizzle.config.ts
33import { defineConfig } from "drizzle-kit";
34
35export default defineConfig({
36 dialect: "sqlite",
37 schema: "./src/schema.ts",
38 out: "./drizzle",
39});
40```
41
42## Generate and commit SQL
43
44```sh
45pnpm exec drizzle-kit generate
46git diff -- drizzle
47git add src/schema.ts drizzle.config.ts drizzle
48git commit -m "Create users table"
49```
50
51Generate migrations when the schema changes; CI deploys these committed files.
52
53## Wire the schema into the database
54
55```typescript
56// src/Db.ts
57import * as Cloudflare from "alchemy/Cloudflare";
58
59export const Database = Cloudflare.D1.Database("Database", {
60 migrations: "./drizzle",
61});
62```
63
64D1 applies pending SQL during deployment. [D1 migrations](/cloudflare/data/d1#migrations) covers database-specific behavior; [Drizzle migrations](/sql/drizzle/migrations) covers file generation.
65
66## Query from the Worker with `Drizzle.D1`
67
68```typescript
69// src/Api.ts
70import * as Cloudflare from "alchemy/Cloudflare";
71import * as Drizzle from "alchemy/Drizzle";
72import * as Effect from "effect/Effect";
73import * as HttpServerResponse from "effect/http/HttpServerResponse";
74import { Database } from "./Db.ts";
75import { users } from "./schema.ts";
76
77export default class Api extends Cloudflare.Worker<Api>()(
78 "Api",
79 { main: import.meta.url },
80 Effect.gen(function* () {
81 const database = yield* Database;
82 const binding = yield* Cloudflare.D1.QueryDatabase(database);
83 const db = yield* Drizzle.D1(binding);
84
85 return {
86 fetch: Effect.gen(function* () {
87 const rows = yield* db.select().from(users).pipe(Effect.orDie);
88 return yield* HttpServerResponse.json({ users: rows });
89 }),
90 };
91 }).pipe(Effect.provide(Cloudflare.D1.QueryDatabaseBinding)),
92) {}
93```
94
95## Define the stack
96
97```typescript
98// alchemy.run.ts
99import * as Alchemy from "alchemy";
100import * as Cloudflare from "alchemy/Cloudflare";
101import * as Effect from "effect/Effect";
102import Api from "./src/Api.ts";
103
104export default Alchemy.Stack(
105 "D1Drizzle",
106 { providers: Cloudflare.providers(), state: Alchemy.localState() },
107 Effect.gen(function* () {
108 const api = yield* Api;
109 return { url: api.url };
110 }),
111);
112```
113
114## Deploy
115
116```sh
117pnpm alchemy deploy
118curl "$WORKER_URL"
119# {"users":[]}
120```
121
122Set `WORKER_URL` to the deployed URL; keep `.alchemy` for later deployments and cleanup.
123
124## Local dev
125
126```sh
127pnpm alchemy dev
128```
129
130The same Worker and binding use local D1 during development.
131
132## Queries and transactions
133
134[Drizzle on D1](/sql/drizzle/d1) covers relational queries; [Effect SQL on D1](/sql/effect-sql/d1) covers tagged-template SQL. D1 uses batch operations rather than interactive transactions or streaming queries.
135
136## Where next
137
138See [SQL databases](/sql/databases), the [D1 resource](/cloudflare/data/d1), or the [existing D1 example](https://github.com/alchemy-run/alchemy/tree/main/examples/cloudflare-d1-drizzle).