← Back to all posts

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.

2 min read
  • 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.