Drizzle ORM

Pathrule2 Rules • 3 Memories • 1 Skill

Guardrails for teams using Drizzle ORM as their type-safe SQL layer. It keeps the TypeScript schema as the single source of truth, enforces a generate-then-migrate workflow with drizzle-kit, and steers queries toward inferred types, relations v2, and safe transactions. Use it to stop schema drift and silent N+1 patterns before they ship.

Suggested path map

Pathrule places each piece on the matching path, so your assistant only sees it where it belongs. This is the scoping you get on import; you can adjust it in your workspace.

/ workspace root
drizzle-orm-review
src/
db/
Drizzle versions and config baseline (2026)
Relations v2: defineRelations and the relational query API
Querying patterns: transactions and prepared statements
schema/
Schema is the single source of truth
drizzle/
Generate then migrate; never push to a shared database

Rules

2
Schema is the single source of truth/src/db/schemahighstrictChange tables in the TypeScript schema, never in the live database; export inferred types, never hand-write row shapes.
1Treat the Drizzle TypeScript schema as the only place a table shape is defined.
2 
3- Edit column and table definitions in `src/db/schema`, then run `drizzle-kit generate` to produce the SQL migration.
4- Never alter columns directly in the database or hand-edit a generated migration's SQL after it is committed; both create a drift that silently breaks generated types.
5- Export inferred types with `typeof users.$inferSelect` and `typeof users.$inferInsert` instead of redeclaring row shapes by hand. Hand-written interfaces fall out of sync whenever a column is added or removed.
6- Define foreign keys, indexes, and constraints in the schema so `drizzle-kit` can diff and version them alongside column changes.
Generate then migrate; never push to a shared database/drizzlehighadvisoryCommit generated SQL migrations and apply them with drizzle-kit migrate; reserve push for throwaway local prototyping only.
1Use the `generate` + `migrate` workflow for anything that touches a shared or production database.
2 
3- Run `drizzle-kit generate` and commit the resulting file in `drizzle/` alongside the schema change in the same PR.
4- Apply migrations with `drizzle-kit migrate` (or the `migrate()` helper at deploy time). Reserve `drizzle-kit push` for local throwaway prototyping only; it overwrites the database schema without recording a migration file.
5- Hand-write the reverse SQL for destructive changes (dropping or retyping a column) and test it on a copy of the staging schema first.
6- Add a CI check that runs `drizzle-kit check` (or equivalent diff) and fails when the schema has a diff with no corresponding migration file, so drift cannot reach main.

Memories

3
Drizzle versions and config baseline (2026)/src/dbCurrent package versions, dialect-based config, and shared db-client conventions.
1Pin to the current Drizzle line and use the dialect-based config introduced in the 0.40+ releases.
2 
3- Runtime is `drizzle-orm` and the CLI is `drizzle-kit`. The stable line as of mid-2026 is 0.4x; a v1.0.0 release is in progress — confirm the exact pinned version in `package.json` before relying on APIs documented only for v1.
4- `drizzle.config.ts` uses `dialect` (`"postgresql"`, `"mysql"`, or `"sqlite"`) plus `schema` and `out` paths; the older `driver` key is removed in the new config format.
5- Keep one shared `db` client instance per process. Pass the `db` (or a `tx`) into functions as a parameter rather than importing a singleton inside deeply nested modules, so transaction boundaries stay explicit.
6- For Postgres, create the client with `drizzle(pool, { schema })` where `pool` is a `node-postgres` Pool or `postgres` (postgres.js) instance. Passing the schema object enables the relational query API.
7 
8See /src/db for querying patterns and /src/db for relations v2 setup.
Relations v2: defineRelations and the relational query API/src/dbHow to declare relations once with defineRelations, pass them to drizzle(), and use the with: {} query API without N+1 loops.
1Relations v2, introduced in Drizzle 0.36+, moves relation declarations out of individual table files and into a centralized definition. Agents frequently write the old per-table `relations()` pattern or skip the relational API entirely and write N+1 loops.
2 
3- Define all relations in a dedicated file (for example `src/db/relations.ts`) using `defineRelations` exported from `drizzle-orm`:
4 ```ts
5 import { defineRelations } from 'drizzle-orm';
6 import * as schema from './schema';
7 
8 export const relations = defineRelations(schema, (r) => ({
9 users: {
10 posts: r.many.posts({ from: schema.users.id, to: schema.posts.authorId }),
11 },
12 posts: {
13 author: r.one.users({ from: schema.posts.authorId, to: schema.users.id }),
14 },
15 }));
16 ```
17- Pass the relations object to `drizzle()` alongside the schema: `const db = drizzle(pool, { schema, relations })`.
18- Query nested data with `db.query.users.findMany({ with: { posts: true } })`. This issues a single SQL query (a lateral join), not a loop of per-row queries. The old per-table `relations()` helper is still supported for compatibility but new code should use `defineRelations`.
19- For optional or filtered eager loads, pass `{ with: { posts: { where: eq(posts.published, true) } } }` rather than filtering in application code after loading all rows.
Querying patterns: transactions and prepared statements/src/dbAtomic transactions with savepoints and prepared statements for hot paths.
1Prefer Drizzle's built-in abstractions for transactions and repeated queries over hand-rolled SQL.
2 
3- Wrap multi-statement writes in `db.transaction(async (tx) => { ... })` and use only `tx` inside the callback. Throwing (or returning a rejected promise) rolls everything back automatically. Nested `db.transaction()` calls on the same `tx` become savepoints.
4- For hot paths, build a prepared statement once with `.prepare('name')` and bind values via `sql.placeholder('name')`, then call `.execute({ name: value })`. Prepared statements send only bind parameters on repeated calls and let Postgres skip re-planning.
5- Reach for the `sql` template tag for expressions Drizzle has no query builder for. Always pass user input as parameters in the template (`sql`SELECT * FROM users WHERE id = ${userId}``), never as interpolated string concatenation.
6- Avoid `db.execute(sql.raw(...))` with unsanitized strings; it bypasses Drizzle's parameter binding and introduces SQL injection.

Skills

1
drizzle-orm-review/rootChecklist to review a Drizzle schema change, migration, and queries before merge.
1---
2name: drizzle-orm-review
3description: Review a Drizzle ORM change before merge. Use on schema modifications, drizzle-kit migrations, relation definitions, query patterns, and transaction logic.
4---
5 
6# Drizzle ORM review
7 
8## Schema and migrations
9- [ ] Schema change lives in `src/db/schema` and the database was not edited directly.
10- [ ] A `drizzle-kit generate` migration is committed in `drizzle/` for every schema diff.
11- [ ] `drizzle-kit push` is not used against any shared or production database.
12- [ ] Destructive changes (drop or retype a column) have tested reverse SQL and a staging plan.
13- [ ] Row types come from `$inferSelect` / `$inferInsert`, not hand-written interfaces.
14 
15## Config and client
16- [ ] `drizzle.config.ts` sets `dialect`, `schema`, and `out`; no legacy `driver` key.
17- [ ] One shared `db` client instance per process; the `db` or `tx` is passed into functions, not re-imported from a singleton.
18 
19## Relations and queries
20- [ ] Relations use `defineRelations` in a centralized file and are passed to `drizzle(pool, { schema, relations })`; no per-table `relations()` on new code.
21- [ ] Nested reads use `db.query.<table>.findMany({ with: { ... } })`, not per-row loops.
22- [ ] Multi-statement writes run inside `db.transaction` using only `tx` internally.
23- [ ] Hot-path queries use `.prepare()` with `sql.placeholder`; user input stays parameterized and `sql.raw` is not used with unsanitized input.

Why this pattern

AI agents drift Drizzle schemas out of sync with the database and write untyped, N+1-prone queries.

Built for Backend and full-stack teams running Drizzle ORM on Postgres, MySQL, or SQLite..

Keeps your assistant from:

  • Editing the database directly instead of regenerating migrations from the schema
  • Hand-writing column types instead of inferring them with $inferSelect and $inferInsert
  • Issuing per-row queries in loops instead of using relations or a single batched query
License
Apache-2.0
Version
1.0.0
Updated
2026-06-09
View source