Prisma vs. Raw SQL: What I Actually Ship
Every ORM debate forgets that the real choice is between being productive and being in control — and that most apps only need to be one of those things.
- PostgreSQL
- Prisma
- Backend
The ORM debate generates strong opinions and very little data. After a few years of shipping PostgreSQL apps with both Prisma and hand-written SQL, I've stopped treating it as a religion and started treating it as a cost model.
Where Prisma earns its keep
Types, migrations, and developer velocity. When a schema is the source of truth, the client is generated, not remembered:
model Project {
id String @id @default(cuid())
title String
description String
tags Tag[]
createdAt DateTime @default(now())
}
I write this once, migrate it, and the query API type-checks against it forever. For CRUD-heavy apps with a team of developers, that's a genuinely large tax removed.
Where I drop to raw SQL
The moment a query stops being simple, the ORM abstraction starts leaking. Reporting queries with window functions, recursive CTEs, or clever indexes read better as SQL — and they often need to.
SELECT date_trunc('month', created_at) AS month,
count(*) FILTER (WHERE status = 'paid') AS paid
FROM orders
GROUP BY month
ORDER BY month;
Trying to express that through a fluent API makes the intent harder to see, not easier.
The rule I ship by
- Simple reads and writes → Prisma. Fast to write, typed, and boring in the good way.
- Anything analytical or deeply relational → raw SQL via a typed query layer.
- Never both for the same hot path — mixed codebases are where the costs compound.
The goal isn't purity. It's that the person maintaining the query — future me — can tell at a glance what the database is being asked to do.