I used Prisma, Sequelize, and TypeORM across different projects over the past four years. I'm now back to writing raw SQL with a thin query builder layer, and I don't plan to go back.
ORMs promise to abstract away the database, but they never actually deliver on this. Every non-trivial query eventually requires you to understand what SQL is being generated. At that point, you're paying the mental overhead of the ORM's query DSL on top of understanding SQL. You end up knowing both, but writing in the more limited one.
Prisma's nested include is delightful until it generates 47 separate queries for a list view. Yes, you can use _count and careful eager loading — but every time I do, I'm mentally translating from what I want (a JOIN) to what the ORM can express. This is not abstracting SQL. It's obscuring it.
postgres.js or better-sqlite3 directly, with zod for result validation. I write the SQL I mean. Query building for dynamic filters is a few utility functions. The total library code is about 200 lines. I've reclaimed full visibility into query plans and it's dramatically easier to optimise.
This won't work for everyone — ORMs add real value for rapid prototyping and migrations. But for a mature product where queries are stable and performance matters, the abstraction tax isn't worth paying.
Comments
Loading comments…