Drizzle ORM Setup
Set up a Drizzle ORM project with typed schema definitions, relations, database client configuration, and migration tooling. Covers PostgreSQL, MySQL, and SQLite (including Neon, Turso, PlanetScale, Cloudflare D1).
When to Use
- Starting a new project that needs a TypeScript ORM
- Adding Drizzle ORM to an existing TypeScript/Node.js project
- Designing database schema with type-safe table definitions
- Setting up relations between tables for the relational query API
- Configuring
drizzle-kit for migration generation and execution
- Migrating from another ORM (Prisma, TypeORM, Knex) to Drizzle
- Setting up Drizzle with a specific database provider (Neon, Turso, PlanetScale, Supabase, Cloudflare D1)
Tools Used
- Read — inspect existing project files (package.json, tsconfig.json, existing schemas)
- Write — create schema, config, and client files
- Edit — modify existing files (add tables, update relations)
- Bash — install dependencies, run drizzle-kit commands
- Glob — find existing schema files, detect project structure
- Grep — search for existing ORM usage, import patterns
Bundled Files
drizzle-orm-setup/
├── references/
│ ├── schema-patterns.md # Table definitions, columns, enums, indexes, relations
│ ├── query-patterns.md # Core SQL-like API + relational query API
│ └── troubleshooting.md # Common errors and fixes
└── templates/
├── schema.ts # Multi-table starter schema with relations
├── drizzle.config.ts # drizzle-kit config (all 3 dialects)
└── db.ts # Database client setup
Workflow
Follow these four phases in order. Stop after each phase to confirm with the user before continuing.
Phase 1: Discover
Goal: Understand the project and choose the right database dialect.
Check for an existing project:
Read package.json → existing deps, scripts, type:"module"
Read tsconfig.json → moduleResolution, target, paths
Glob **/*.ts → project structure
Detect any existing ORM or database usage:
Grep "prisma|typeorm|knex|sequelize|drizzle" in package.json
Grep "pg|mysql2|better-sqlite3|@libsql|@neondatabase" in package.json
Determine the database dialect. Ask the user if not obvious:
- PostgreSQL →
drizzle-orm + pg (or postgres, @neondatabase/serverless, @vercel/postgres)
- MySQL →
drizzle-orm + mysql2 (or @planetscale/database)
- SQLite →
drizzle-orm + better-sqlite3 (or @libsql/client for Turso, @cloudflare/workers-types for D1)
Determine the schema organization:
- Single
schema.ts file (small projects)
schema/ directory with one file per table (recommended for >3 tables)
STOP. Confirm dialect, driver, and schema organization with the user.
Phase 2: Schema
Goal: Create typed table definitions and relations.
Read references/schema-patterns.md for the full pattern reference.
Create the schema file(s). Use the appropriate table constructor:
- PostgreSQL:
pgTable(), pgEnum()
- MySQL:
mysqlTable(), mysqlEnum()
- SQLite:
sqliteTable()
For each table, define:
- Columns with types and constraints (
.notNull(), .default(), .unique(), .references())
- Primary key (
.primaryKey() or .generatedAlwaysAsIdentity())
- Indexes via the third argument:
(table) => [index('name').on(table.col)]
- Timestamps using the shared pattern from
references/schema-patterns.md
Define relations in the same file (or a separate relations.ts):
import { relations } from 'drizzle-orm';
export const usersRelations = relations(users, ({ many }) => ({
posts: many(posts),
}));
Use templates/schema.ts as a starter if building from scratch.
Key rules:
- Relations are for the relational query API only. They do NOT create foreign keys.
- Foreign keys are defined via
.references(() => otherTable.id) on the column.
- Always define BOTH sides of a relation (e.g.,
users → many(posts) AND posts → one(users)).
- Use
$onUpdateFn(() => new Date()) for updatedAt columns, NOT database-level triggers.
STOP. Review the schema with the user. Confirm table structure, column types, and relations.
Phase 3: Client + Config
Goal: Set up the database connection and drizzle-kit configuration.
Read templates/db.ts and templates/drizzle.config.ts for the starter patterns.
Create the database client file (src/db/index.ts or src/db.ts):
import { drizzle } from 'drizzle-orm/node-postgres';
import * as schema from './schema';
export const db = drizzle(process.env.DATABASE_URL!, { schema });
- The
{ schema } option enables the relational query API (db.query.*).
- Without it, only the core SQL-like API works (
db.select(), db.insert(), etc.).
Create drizzle.config.ts at project root:
import { defineConfig } from 'drizzle-kit';
export default defineConfig({
dialect: 'postgresql', // or 'mysql' or 'sqlite'
schema: './src/db/schema.ts',
out: './drizzle',
dbCredentials: {
url: process.env.DATABASE_URL!,
},
});
Install dependencies:
# Core (always needed)
npm install drizzle-orm
# Dev tooling (always needed)
npm install -D drizzle-kit
# Database driver (pick one)
npm install pg # PostgreSQL (node-postgres)
npm install @neondatabase/serverless # Neon serverless
npm install mysql2 # MySQL
npm install better-sqlite3 # SQLite
npm install @libsql/client # Turso / libSQL
Add scripts to package.json:
{
"scripts": {
"db:generate": "drizzle-kit generate",
"db:migrate": "drizzle-kit migrate",
"db:push": "drizzle-kit push",
"db:studio": "drizzle-kit studio"
}
}
STOP. Confirm the client setup and config. Verify the DATABASE_URL is available (env var, .env file, etc.).
Phase 4: Migrate
Goal: Generate and run the initial migration.
Choose the migration strategy:
drizzle-kit push — Direct schema push. Good for prototyping and local dev. No migration files.
drizzle-kit generate + drizzle-kit migrate — Generates SQL migration files in ./drizzle/. Use for production.
For production migrations, run:
npx drizzle-kit generate # Creates SQL files in ./drizzle/
npx drizzle-kit migrate # Applies pending migrations
Verify the migration:
# Check generated SQL
ls ./drizzle/
cat ./drizzle/0000_*.sql
# Open Drizzle Studio to inspect
npx drizzle-kit studio
For programmatic migrations (CI/CD, startup scripts):
import { migrate } from 'drizzle-orm/node-postgres/migrator';
import { db } from './db';
await migrate(db, { migrationsFolder: './drizzle' });
Important notes:
- Drizzle does NOT support migration rollbacks. To undo, create a new migration that reverses the changes.
push is lossy — it may drop and recreate columns/tables. Never use on production data.
- Migration files are append-only. Don't edit generated SQL files.
- The
./drizzle/meta/ directory tracks migration state. Commit it to version control.
STOP. Confirm migrations ran successfully. Check for any errors.
Troubleshooting Quick Reference
Read references/troubleshooting.md for detailed solutions.
| Symptom |
Likely Cause |
Quick Fix |
Type instantiation is excessively deep |
Too many tables/relations in one file |
Split schema into multiple files, use satisfies |
Relation not found |
Missing relation definition |
Define both sides of every relation |
Cannot find module 'drizzle-orm/...' |
Wrong dialect import |
Match import path to your dialect (e.g., drizzle-orm/pg-core) |
push drops a column unexpectedly |
Column rename detected as drop+add |
Use generate + edit the SQL migration manually |
Column does not exist after migration |
Schema and DB out of sync |
Run drizzle-kit introspect to check actual DB state |
| Circular import errors |
Relations importing from each other |
Put all relations in one file or use barrel exports |
Architecture Notes
- Schema-as-code: Drizzle schemas are plain TypeScript. No DSL, no code generation step. The schema IS the source of truth.
- Two query APIs: Core API (
db.select().from()) for SQL-like control. Relational API (db.query.users.findMany()) for nested data loading. Both are fully typed.
- Relations are virtual:
relations() definitions exist only in TypeScript. They tell the relational query API how to join tables but create no database constraints. Foreign keys are separate.
- drizzle-kit is the CLI: It reads your TypeScript schema, diffs it against the database (or prior migrations), and generates SQL. It is a dev dependency only — not needed at runtime.
- Multi-database: Same API patterns across PostgreSQL, MySQL, and SQLite. Column type imports differ (
pg-core, mysql-core, sqlite-core) but the shape is identical.
Output Summary
After completing all phases, the user should have:
1---2name: drizzle-orm-setup3description: Scaffold a Drizzle ORM project with TypeScript schema, relations, database client, and migrations. Use for: setting up Drizzle from scratch, designing table schemas, configuring drizzle-kit, writing type-safe queries, multi-database support (PostgreSQL, MySQL, SQLite). Triggers: drizzle, drizzle orm, drizzle setup, drizzle schema, drizzle migration, drizzle-kit, typescript orm, sql-like orm, drizzle config, drizzle relations, drizzle project.4---5
6# Drizzle ORM Setup
7
8Set up a Drizzle ORM project with typed schema definitions, relations, database client configuration, and migration tooling. Covers PostgreSQL, MySQL, and SQLite (including Neon, Turso, PlanetScale, Cloudflare D1).
9
10## When to Use
11
12- Starting a new project that needs a TypeScript ORM
13- Adding Drizzle ORM to an existing TypeScript/Node.js project
14- Designing database schema with type-safe table definitions
15- Setting up relations between tables for the relational query API
16- Configuring `drizzle-kit` for migration generation and execution
17- Migrating from another ORM (Prisma, TypeORM, Knex) to Drizzle
18- Setting up Drizzle with a specific database provider (Neon, Turso, PlanetScale, Supabase, Cloudflare D1)
19
20## Tools Used
21
22- **Read** — inspect existing project files (package.json, tsconfig.json, existing schemas)
23- **Write** — create schema, config, and client files
24- **Edit** — modify existing files (add tables, update relations)
25- **Bash** — install dependencies, run drizzle-kit commands
26- **Glob** — find existing schema files, detect project structure
27- **Grep** — search for existing ORM usage, import patterns
28
29## Bundled Files
30
31```
32drizzle-orm-setup/
33├── references/
34│ ├── schema-patterns.md # Table definitions, columns, enums, indexes, relations
35│ ├── query-patterns.md # Core SQL-like API + relational query API
36│ └── troubleshooting.md # Common errors and fixes
37└── templates/
38 ├── schema.ts # Multi-table starter schema with relations
39 ├── drizzle.config.ts # drizzle-kit config (all 3 dialects)
40 └── db.ts # Database client setup
41```
42
43## Workflow
44
45Follow these four phases in order. **Stop after each phase** to confirm with the user before continuing.
46
47---
48
49### Phase 1: Discover
50
51**Goal:** Understand the project and choose the right database dialect.
52
531. Check for an existing project:
54 ```
55 Read package.json → existing deps, scripts, type:"module"
56 Read tsconfig.json → moduleResolution, target, paths
57 Glob **/*.ts → project structure
58 ```
59
602. Detect any existing ORM or database usage:
61 ```
62 Grep "prisma|typeorm|knex|sequelize|drizzle" in package.json
63 Grep "pg|mysql2|better-sqlite3|@libsql|@neondatabase" in package.json
64 ```
65
663. Determine the database dialect. Ask the user if not obvious:
67 - **PostgreSQL** → `drizzle-orm` + `pg` (or `postgres`, `@neondatabase/serverless`, `@vercel/postgres`)
68 - **MySQL** → `drizzle-orm` + `mysql2` (or `@planetscale/database`)
69 - **SQLite** → `drizzle-orm` + `better-sqlite3` (or `@libsql/client` for Turso, `@cloudflare/workers-types` for D1)
70
714. Determine the schema organization:
72 - Single `schema.ts` file (small projects)
73 - `schema/` directory with one file per table (recommended for >3 tables)
74
75> **STOP.** Confirm dialect, driver, and schema organization with the user.
76
77---
78
79### Phase 2: Schema
80
81**Goal:** Create typed table definitions and relations.
82
831. Read `references/schema-patterns.md` for the full pattern reference.
84
852. Create the schema file(s). Use the appropriate table constructor:
86 - PostgreSQL: `pgTable()`, `pgEnum()`
87 - MySQL: `mysqlTable()`, `mysqlEnum()`
88 - SQLite: `sqliteTable()`
89
903. For each table, define:
91 - **Columns** with types and constraints (`.notNull()`, `.default()`, `.unique()`, `.references()`)
92 - **Primary key** (`.primaryKey()` or `.generatedAlwaysAsIdentity()`)
93 - **Indexes** via the third argument: `(table) => [index('name').on(table.col)]`
94 - **Timestamps** using the shared pattern from `references/schema-patterns.md`
95
964. Define relations in the same file (or a separate `relations.ts`):
97 ```typescript
98 import { relations } from 'drizzle-orm';
99
100 export const usersRelations = relations(users, ({ many }) => ({
101 posts: many(posts),
102 }));
103 ```
104
1055. Use `templates/schema.ts` as a starter if building from scratch.
106
107**Key rules:**
108- Relations are for the relational query API only. They do NOT create foreign keys.
109- Foreign keys are defined via `.references(() => otherTable.id)` on the column.
110- Always define BOTH sides of a relation (e.g., `users → many(posts)` AND `posts → one(users)`).
111- Use `$onUpdateFn(() => new Date())` for `updatedAt` columns, NOT database-level triggers.
112
113> **STOP.** Review the schema with the user. Confirm table structure, column types, and relations.
114
115---
116
117### Phase 3: Client + Config
118
119**Goal:** Set up the database connection and drizzle-kit configuration.
120
1211. Read `templates/db.ts` and `templates/drizzle.config.ts` for the starter patterns.
122
1232. Create the database client file (`src/db/index.ts` or `src/db.ts`):
124 ```typescript
125 import { drizzle } from 'drizzle-orm/node-postgres';
126 import * as schema from './schema';
127
128 export const db = drizzle(process.env.DATABASE_URL!, { schema });
129 ```
130 - The `{ schema }` option enables the relational query API (`db.query.*`).
131 - Without it, only the core SQL-like API works (`db.select()`, `db.insert()`, etc.).
132
1333. Create `drizzle.config.ts` at project root:
134 ```typescript
135 import { defineConfig } from 'drizzle-kit';
136
137 export default defineConfig({
138 dialect: 'postgresql', // or 'mysql' or 'sqlite'
139 schema: './src/db/schema.ts',
140 out: './drizzle',
141 dbCredentials: {
142 url: process.env.DATABASE_URL!,
143 },
144 });
145 ```
146
1474. Install dependencies:
148 ```bash
149 # Core (always needed)
150 npm install drizzle-orm
151
152 # Dev tooling (always needed)
153 npm install -D drizzle-kit
154
155 # Database driver (pick one)
156 npm install pg # PostgreSQL (node-postgres)
157 npm install @neondatabase/serverless # Neon serverless
158 npm install mysql2 # MySQL
159 npm install better-sqlite3 # SQLite
160 npm install @libsql/client # Turso / libSQL
161 ```
162
1635. Add scripts to `package.json`:
164 ```json
165 {
166 "scripts": {
167 "db:generate": "drizzle-kit generate",
168 "db:migrate": "drizzle-kit migrate",
169 "db:push": "drizzle-kit push",
170 "db:studio": "drizzle-kit studio"
171 }
172 }
173 ```
174
175> **STOP.** Confirm the client setup and config. Verify the DATABASE_URL is available (env var, .env file, etc.).
176
177---
178
179### Phase 4: Migrate
180
181**Goal:** Generate and run the initial migration.
182
1831. Choose the migration strategy:
184 - **`drizzle-kit push`** — Direct schema push. Good for prototyping and local dev. No migration files.
185 - **`drizzle-kit generate` + `drizzle-kit migrate`** — Generates SQL migration files in `./drizzle/`. Use for production.
186
1872. For production migrations, run:
188 ```bash
189 npx drizzle-kit generate # Creates SQL files in ./drizzle/
190 npx drizzle-kit migrate # Applies pending migrations
191 ```
192
1933. Verify the migration:
194 ```bash
195 # Check generated SQL
196 ls ./drizzle/
197 cat ./drizzle/0000_*.sql
198
199 # Open Drizzle Studio to inspect
200 npx drizzle-kit studio
201 ```
202
2034. For programmatic migrations (CI/CD, startup scripts):
204 ```typescript
205 import { migrate } from 'drizzle-orm/node-postgres/migrator';
206 import { db } from './db';
207
208 await migrate(db, { migrationsFolder: './drizzle' });
209 ```
210
211**Important notes:**
212- Drizzle does NOT support migration rollbacks. To undo, create a new migration that reverses the changes.
213- `push` is lossy — it may drop and recreate columns/tables. Never use on production data.
214- Migration files are append-only. Don't edit generated SQL files.
215- The `./drizzle/meta/` directory tracks migration state. Commit it to version control.
216
217> **STOP.** Confirm migrations ran successfully. Check for any errors.
218
219---
220
221## Troubleshooting Quick Reference
222
223Read `references/troubleshooting.md` for detailed solutions.
224
225| Symptom | Likely Cause | Quick Fix |
226|---------|-------------|-----------|
227| `Type instantiation is excessively deep` | Too many tables/relations in one file | Split schema into multiple files, use `satisfies` |
228| `Relation not found` | Missing relation definition | Define both sides of every relation |
229| `Cannot find module 'drizzle-orm/...'` | Wrong dialect import | Match import path to your dialect (e.g., `drizzle-orm/pg-core`) |
230| `push` drops a column unexpectedly | Column rename detected as drop+add | Use `generate` + edit the SQL migration manually |
231| `Column does not exist` after migration | Schema and DB out of sync | Run `drizzle-kit introspect` to check actual DB state |
232| Circular import errors | Relations importing from each other | Put all relations in one file or use barrel exports |
233
234## Architecture Notes
235
236- **Schema-as-code**: Drizzle schemas are plain TypeScript. No DSL, no code generation step. The schema IS the source of truth.
237- **Two query APIs**: Core API (`db.select().from()`) for SQL-like control. Relational API (`db.query.users.findMany()`) for nested data loading. Both are fully typed.
238- **Relations are virtual**: `relations()` definitions exist only in TypeScript. They tell the relational query API how to join tables but create no database constraints. Foreign keys are separate.
239- **drizzle-kit is the CLI**: It reads your TypeScript schema, diffs it against the database (or prior migrations), and generates SQL. It is a dev dependency only — not needed at runtime.
240- **Multi-database**: Same API patterns across PostgreSQL, MySQL, and SQLite. Column type imports differ (`pg-core`, `mysql-core`, `sqlite-core`) but the shape is identical.
241
242## Output Summary
243
244After completing all phases, the user should have:
245
246- [ ] Schema file(s) with typed table definitions and relations
247- [ ] Database client (`db.ts`) with schema-aware `drizzle()` instance
248- [ ] `drizzle.config.ts` at project root
249- [ ] Dependencies installed (`drizzle-orm` + `drizzle-kit` + driver)
250- [ ] npm scripts for generate/migrate/push/studio
251- [ ] Initial migration generated and applied (or schema pushed)