Why ORMs matter again in the vibe-coding era
A few years ago, ORMs were easy to throw under the bus. In interviews and internal talks, “we skip the ORM and write SQL by hand” sometimes sounded like a flex. If you admitted you leaned on an ORM, you felt like you had to justify yourself — as if it meant you did not understand the database.
That vibe is softer now. Teams ship dozens of files in a day with chat and agents. When code grows that fast, something that pins where the app meets the database — schema on disk, migrations in git, a generated client — starts to feel less like training wheels and more like infrastructure. The brand name might be Prisma, TypeORM, Django, whatever; the pattern is the same.
This is not cheerleading or a takedown. It is my read on why the layer got snubbed for a while, and why vibe coding pushed it back up. I kept the tail end short on purpose.
What ORMs actually do
Tables and objects disagree on how to speak. An ORM translates between them. When it works, CRUD is fast and patterns line up across the team. When it does not, SQL multiplies in the shadows and you only notice after traffic arrives.
That part never changed. What changed is how fast non-human “authors” pile code on top.
Why ORMs were looked down on
There were plenty of good reasons people rolled their eyes: three lines of app code, thirty SELECTs in the log; conference posts with execution plans and raw SQL that looked sharp next to boring ORM defaults; unique keys and indexes glossed over until something broke; Hibernate XML or TypeORM decorator soup so “ORM” meant “heavy.” Query builders and handwritten SQL often looked like the grown-up choice—and often were.
What vibe coding changed
Agents are fast and plausible when wrong. One bad table name or join direction can look fine locally and explode in staging or prod.
The same layer people used to mock now solves a different problem. A schema file gives the model something to follow; a single schema.prisma becomes a shared map. Migrations as commits turn “why does prod look like this?” into a diff story. Generated clients catch invented method names at build time; string-built SQL does not.
So you hear less “we must use an ORM” and more “we need schema and migrations locked down like this.” The irony: people who used to flex raw SQL now argue for visible boundaries when the author is not human.
“The model writes SQL fine” is true for a demo. When the repo grows and people rotate, “whatever the chat said that day” does not scale. What rose in status is less the word ORM and more durable schema.
Vibe coding in practice: real examples
Not a checklist of “best practices”—short stories of how teams actually wire agent-assisted coding into repos that use an ORM. Names differ; the shape repeats.
Example 1 — Put the schema in the chat
In Cursor, attaching @schema.prisma before “add an orders API” cuts bogus table names sharply. Without it, the model often emits Prisma calls against tables like user_profiles that do not exist. If dev is SQLite-only, that code can even look fine locally and blow up in staging.
Some teams add rules so the agent still gets a floor when context is thin:
The “map” you want the model to follow is basically this shape:
Example 2 — Merge migrations first
When one PR mixes ALTERs and service code, reviewers skim the SQL. Some teams open a migrations-only PR first (prisma/migrations/, alembic/, etc.), merge it, then tell the agent “implement resolvers/services on this branch.” Long-lock changes get operational attention in that first PR.
Local flow often looks like:
Example 3 — CI prisma generate caught it
Agents invent prisma.user.findByMagic(); local lint alone may not care. CI that runs prisma generate then tsc (or build) turns that into a red merge. It also catches “just slap any on it” PRs early.
Teams that only use package.json sometimes prepend generate: "build": "prisma generate && tsc".
Example 4 — Same feature, different prompt
Before: “Add a list API with orders and customers.”
After: “Use only Order and Customer from schema.prisma; load the list without N+1 (one round of relation/include).” The second can still be wrong, but reviewers have a clear reason to read schema + generated SQL together.
You often see agent output split like this:
Example 5 — Staying on legacy TypeORM for now
Before a big migration, teams expose only the existing entity files and scope: no new tables; only patterns allowed on these entities. That blocks one file from turning into a mix of dialects and decorators.
Giving the agent a concrete outline in code helps:
Takeaway: vibe coding without rails lets an ORM hide hallucinated SQL even better than hand-written code. Pinning work to schema, PR shape, and CI is how those “real examples” turn into a repeatable way of working—not a one-off trick.
Cautions when you ship this for real
Production breaks usually trace to one of the buckets below. Teams that lean on agents should turn most of these into written rules early.
N+1 and relation loading
If list endpoints do not batch relations, query count creeps up quietly. ORMs name it include, preload, with, etc.—pick a default pattern for list APIs and put it in docs or a review checklist. Paste the same constraint into agent prompts.
Migrations
Running migrate only on a laptop keeps Friday-night surprises alive. Apply migrations to a test database in CI; long-lock ALTERs get their own deploy window and runbook. Human-review diffs for any migration the model generates.
Connection pools
Hidden pools still multiply by instance count × pool size. Serverless and autoscaling often push you toward poolers (e.g. PgBouncer). If staging “runs out of connections” but dev does not, start here.
Raw SQL
Window functions, CTEs, and partial indexes often force you out of the ORM. Agree where raw is allowed in the tree; use parameter binding, not string concat. Prisma’s $queryRaw family is the usual escape hatch.
Test databases
SQLite-only CI misses Postgres constraints, locking, and types. At least one job on the same engine as production pays for itself.
Read replicas
Read/write routing in the ORM brings the stale read contract from our read replica guide straight into app code. If you cannot promise “reads are always fresh,” you need routing or invalidation rules—not hope.
Org fit
Strong DBA culture often clashes with “the app owns the schema.” Small teams often move faster when schema lives in-repo and pairs well with agents. The label on the ORM matters less than who approves schema and who merges migrations.
Using an ORM well does not mean skipping SQL; it means letting the tool handle repetition and closing bottlenecks and integrity in schema and SQL. Agents make that gap show up faster.
Closing
ORMs sat in the unfashionable bucket for a while — too slow, too hidden, not the hero of the “real SQL” story. Vibe coding made code volume spike; schema that survives, reviews, and fails builds climbed back toward the center. I would not call it a triumphal comeback so much as moving from punching bag to something teams actually insist on.
If you start greenfield, put three lines in the README: how you stop N+1, who reviews migrations, where raw SQL is allowed. Human or agent, without that you slide back to the old reputation.