Postgres + Drizzle, after a year
Not a tutorial. Lessons from a year of running Drizzle in production across JomJual, Lunara, and a few side builds.
· 4 min read
A year ago I migrated a side project from Prisma to Drizzle over one long Saturday. I have not gone back. This is not a Drizzle vs Prisma post. This is what Drizzle looks like once the honeymoon is over and you are actually running the thing.
What I love
The schema is just TypeScript. That sounds like nothing until you have gone through a Prisma migration where the generator hangs on a Windows filesystem and you cannot explain to your cofounder why a no-code schema change is blocking deployment.
pgTable('orders', { ... }) is ugly. It is also a function call. I can loop over it. I can factor shared columns into a helper. I can write a test that asserts every tenant-scoped table has a store_id foreign key, and the test runs before any migration goes near Neon. Try doing that with a .prisma file.
What I had to unlearn
Prisma's relation helpers hide N+1 under a friendly API. Drizzle does not. with is a real join, and you have to think about it. The first time I shipped a product listing that did 31 round-trips for "select products with variants" I learned this the hard way, at 22:00, staring at Neon query logs.
The fix was reading the docs — Drizzle's with generates a single query with lateral joins when you configure relations properly. But the default when you forget is not magic. It is slow. You get what you asked for.
Migrations
drizzle-kit generate is fast and deterministic. drizzle-kit push is fine for early development, and dangerous after your first user. Once the database has real data, every migration goes through generate → review the SQL → run in a transaction.
I still use push for local iteration. I do not use it in CI. There is no world in which an unreviewed ALTER TABLE hits production.
The actual ergonomics win
Type-safe query builders are a meme category now, but the one that changed my life is the query response type. When I select five fields from three joined tables with a filter, the returned row is typed exactly for those five fields. No any, no partial, no as unknown as SomeType. You change the select, the consumer breaks at compile time, you fix it, you move on. This is the actual reason to use Drizzle.
Would I pick it again
Yes. But I would also not judge anyone who picks Prisma. The right answer for most people is "the one your team already knows." Pick something and ship. Database tools are a worse rabbit hole than editors.
Keep reading
- sreobservability
Why Your Error Budget Is Lying to You (And How Observability Actually Fixes It)
Error budgets sound clean in theory but fail silently when you can't see where the unreliability actually is. Here's how to build observability that makes them real.
- databaseapi
When Your Team Stops Talking to the Database Directly — Why That Matters
Direct database access feels fast until someone deletes a year of records by accident. Here's what happens when you add an API layer, what it costs, and the exact warning signs your business has outgrown the shortcut.
- aiworkflow
Claude Code, but for backend people
Not a demo reel. How I actually use Claude Code for queue workers, schema migrations, and payment code where wrong answers cost RM.