Schema Definition
- •Export every table from schema file — queries fail silently if table isn't exported
- •Use
$inferSelectfor query return types,$inferInsertfor insert input — they differ (select has defaults filled, insert has optionals) - •Define
relations()in a separate call, not inline with table — Drizzle separates schema from relations
Query Syntax Traps
- •Conditions use functions, not objects:
where: eq(users.id, 5)notwhere: { id: 5 }— Prisma syntax doesn't work - •Combine conditions with
and()/or():where: and(eq(users.active, true), gt(users.age, 18)) - •
db.query.users.findMany()for relational queries withwith:,db.select().from(users)for SQL-like — mixing them causes type errors
Migrations
- •
drizzle-kit pushis dev-only (destructive) — production needsdrizzle-kit generatethendrizzle-kit migrate - •Schema changes require regenerating migrations — editing generated SQL breaks the migration hash
- •Set
strict: truein drizzle.config.ts to catch schema drift before it hits production
Driver-Specific
- •PostgreSQL: use
pgTable, imports fromdrizzle-orm/pg-core - •MySQL: use
mysqlTable, imports fromdrizzle-orm/mysql-core - •SQLite: use
sqliteTable, imports fromdrizzle-orm/sqlite-core - •Mixing imports across drivers compiles but fails at runtime with cryptic errors
Performance
- •Wrap multi-query operations in
db.transaction(async (tx) => {})— Drizzle doesn't auto-batch - •Use
.prepare()for queries executed repeatedly — skips query building overhead - •Add
.limit()to everyfindMany()/select()— no default limit means full table scans
Common Mistakes
- •Forgetting
awaiton queries returns a Promise, not results — TypeScript doesn't catch this if you ignore the return - •
returning()is required to get inserted/updated rows back — without it you get{ rowCount }only - •JSON columns: PostgreSQL uses
jsonb(), MySQL usesjson()— wrong function = wrong serialization