Drizzle ORM in Production: Patterns I Use After 6 Client Projects
Real Drizzle patterns from shipping it on 6 production apps in the last year — schema design, type safety, migrations, query patterns, and where Drizzle still falls short of Prisma.
Why Drizzle, why now
I have used Prisma since 2020 and shipped it on 20 plus projects. In the last year I switched my default to Drizzle on 6 new projects. This post is what I learned and where Drizzle still falls behind.
The short version: Drizzle wins on type safety, bundle size, and SQL transparency. Prisma still wins on developer experience for teams new to ORMs, and on the ecosystem around it (Studio, Accelerate, etc.). Pick by team and use case.
What Drizzle does better
SQL transparency
The Drizzle query you write looks like SQL. When something is slow, you can guess what it compiles to without running explain. With Prisma, the generated SQL is sometimes surprising — a findMany with a nested include can be 4 joins or 4 queries depending on the driver.
This matters when debugging. I have spent fewer hours on "why is this query slow" since switching to Drizzle.
Type inference
Drizzle's TypeScript inference is the cleanest I have seen in any ORM. You define your schema, and every query returns a fully-typed result with no codegen step.
With Prisma, you have to run prisma generate after every schema change. Drizzle uses TypeScript's type system natively. No generate, no stale types, no codegen race conditions in CI.
Bundle size
Drizzle's runtime is small enough to run in edge functions. Prisma's query engine is a Rust binary that does not fit in a Cloudflare Worker. If you are deploying to the edge, Drizzle is the only real choice.
What Prisma still does better
Studio
Prisma Studio is a beautiful database GUI that ships with the ORM. Drizzle has Drizzle Studio now, but it is less polished and missing some Prisma features.
Migration ergonomics
Prisma's migration tool — just type prisma migrate dev — is friendlier than Drizzle's two-step (generate then push). For a team new to migrations, Prisma is gentler.
Documentation depth
Prisma has years of docs, tutorials, and Stack Overflow answers. Drizzle is younger and the ecosystem is still catching up. For a junior engineer, Prisma is easier to onboard.
Schema design that scales
A pattern I use on every Drizzle project: schema lives in its own file per resource (users.ts, orders.ts, etc.), each table defined with pgTable, and types exported via $inferSelect for reads and $inferInsert for writes.
Three things matter:
For multi-tenant apps, add a tenantId column on every resource and a foreign key to the tenant table. This is the foundation for row-level isolation (see my SaaS multi-tenant post).
Query patterns
The repository pattern
I wrap Drizzle queries in a per-resource repository module. Keeps query logic out of route handlers, makes testing easier.
A typical setup: a usersRepo module exports functions like findById(id) and listByTenant(tenantId). Route handlers call usersRepo.listByTenant — they never touch Drizzle directly. This is the single best architectural choice for keeping a Drizzle codebase maintainable past a year.
Transactions
Drizzle transactions are clean. Wrap your work in db.transaction(async (tx) and use tx for all the inserts and updates inside. If anything throws inside the callback, everything rolls back. Use transactions whenever you write to more than one table for a single user action.
Joins vs nested queries
Drizzle gives you both. Two queries with a manual join is sometimes faster than one query with relations, depending on your data shape. Measure with explain analyze before choosing.
For most cases, relational queries are fine and read more clearly. Reach for raw joins when you have a specific perf reason.
Migrations
Drizzle's migration flow:
The review step matters. Drizzle's generator is good but not perfect for destructive changes (renaming a column, splitting a table). Always read the SQL before applying.
For production, run migrations as a separate step before deploying app code. Never let the app auto-migrate on boot — too many race conditions, too many ways to corrupt prod.
Where Drizzle still bites you
A few things that are genuinely awkward:
Polymorphic relations
Drizzle does not have first-class polymorphic relations. If you want a "comments" table that can attach to either a "post" or a "user", you write the joining logic by hand.
Prisma has the same limitation, to be fair. Both ORMs assume relational data has fixed shapes.
Computed fields
If you want a fullName field computed from firstName plus lastName, you write it in TypeScript after the query, or in a SQL view. Neither ORM has clean syntax for this.
Soft deletes
No first-class support. You add a deletedAt column and remember to filter on it in every query. I write a helper that wraps common reads with the filter to avoid forgetting.
Drizzle plus Next.js Server Actions
The combination has become my default for new SaaS in 2026. A Server Action calls the repo function, the repo runs the Drizzle query, and types flow all the way from the DB schema to the client component. No API route, no fetch, no manual type definitions.
My Drizzle stack in 2026
TL;DR
If you are starting a new TypeScript backend and want a senior to pick between Drizzle and Prisma for your specific case, or to architect the data layer, contact me.
You might also like
PostgreSQL vs MongoDB for SaaS in 2026: When to Pick Which
After shipping SaaS apps on both Postgres and MongoDB, here's the decision tree I actually use — schema flexibility, JSONB, indexing, costs, and migration pain.
Vector Databases for RAG in 2026: pgvector, Pinecone, or Something Else
After building RAG pipelines on 4 production apps with three different vector stores, here is the framework I use to decide between pgvector, Pinecone, and the new wave of dedicated vector DBs.
Type-Safe Environment Variables in Next.js (And Why You Need Them)
A small chunk of TypeScript that prevents an entire class of production bugs. Validating environment variables at build time with Zod and t3-env.