Claude Code for Drizzle ORM — Workflow Guide
The Setup
You are using Drizzle ORM as a lightweight, type-safe SQL query builder for TypeScript. Unlike heavier ORMs, Drizzle stays close to SQL while providing full TypeScript inference. Claude Code can generate Drizzle schemas and queries, but it consistently defaults to Prisma syntax or writes raw SQL strings.
What Claude Code Gets Wrong By Default
-
Uses Prisma’s
schema.prismafile format. Claude writesmodel User { id Int @id @default(autoincrement()) }. Drizzle defines schemas in TypeScript:export const users = pgTable('users', { id: serial('id').primaryKey() }). -
Calls Prisma client methods. Claude writes
prisma.user.findMany({ where: { age: { gte: 18 } } }). Drizzle uses SQL-like builder syntax:db.select().from(users).where(gte(users.age, 18)). -
Generates raw SQL strings. For complex queries, Claude falls back to
db.execute(sql\SELECT…`)`. Drizzle’s query builder handles joins, subqueries, and CTEs — raw SQL should be a last resort. -
Imports the wrong operators. Claude uses JavaScript operators in where clauses like
users.age > 18. Drizzle requires imported comparison functions:gt(users.age, 18),eq(),like(),and(),or().
The CLAUDE.md Configuration
# Drizzle ORM Project
## Database
- ORM: Drizzle ORM (drizzle-orm)
- Driver: PostgreSQL (pg) or libSQL
- Schema: src/db/schema.ts
- Migrations: drizzle-kit generate + drizzle-kit migrate
## Drizzle Rules
- Schema in TypeScript using pgTable(), sqliteTable()
- Operators: eq, gt, lt, gte, lte, like, and, or from drizzle-orm
- Select: db.select().from(table).where(...)
- Insert: db.insert(table).values({...}).returning()
- Update: db.updateTable(table).set({...}).where(...)
- Delete: db.delete(table).where(...)
- Relations: defined with relations() function separately
- Types: typeof table.$inferSelect, typeof table.$inferInsert
## Conventions
- Schema: src/db/schema.ts or src/db/schema/ directory
- Migrations: drizzle/ directory
- DB instance: src/db/index.ts exports configured drizzle()
- Config: drizzle.config.ts for drizzle-kit
- Use query API for simple reads: db.query.users.findMany()
- Use select API for complex queries: db.select().from()
Workflow Example
You want to query users with their related posts and comment counts. Prompt Claude Code:
“Write a Drizzle ORM query that fetches users along with their total post count and most recent post title. Only include users with at least one post, ordered by post count descending.”
Claude Code should use db.select() with a left join or subquery, count(posts.id) for the aggregate, max(posts.createdAt) for the most recent post, grouped by user ID with a having clause for minimum post count, all using Drizzle’s operator imports.
Common Pitfalls
-
Confusing the query API with the select API. Claude uses
db.query.users.findMany()(relational) for complex aggregations. The query API is for simple relational reads. Usedb.select().from()for joins, aggregates, and subqueries. -
Schema push vs generate confusion. Claude uses
drizzle-kit pushin production. Push applies schema changes directly without creating migration files. Usedrizzle-kit generateto create tracked migration files for production deployments. -
Type inference for joins. Claude manually types the result of join queries. Drizzle infers join result types automatically, but only when you specify selected columns. Use
.select({ user: users, postCount: count(posts.id) })for properly typed results.