| slug: | orm-vs-raw-sql |
|---|---|
| date: | Mar 10, 2026 |
| readTime_en: | 6 min read |
| readTime_pt: | 6 min leitura |
| title_en: | Why I stopped using ORM and went back to raw SQL |
| title_pt: | Por que deixei de usar ORM e voltei ao SQL direto |
| excerpt_en: | After years of Sequelize and Prisma, I hit a wall with complex queries. Here's what I learned switching back to pg and writing SQL by hand. |
| excerpt_pt: | Depois de anos com Sequelize e Prisma, cheguei a um limite com queries complexas. O que aprendi ao voltar ao pg e escrever SQL Γ mΓ£o. |
| tags: | Node.js, PostgreSQL, Backend |
I've used Sequelize, TypeORM, and Prisma across different projects. For simple CRUD apps they're great β you get type safety, migrations, and a nice API out of the box. But once your queries grow beyond a few joins, things start to fall apart.
The turning point for me was a reporting query at Dragonboat. It involved multiple CTEs, window functions, and conditional aggregations. The Prisma version was a wall of `$queryRaw` with TypeScript fighting me every step. The raw SQL version was clean, readable, and 3x faster.
I moved to postgres (the `sql` package by porsager). It gives you tagged template literals with automatic parameterisation, so SQL injection is still handled correctly:
```ts const users = await sql` SELECT id, name, email FROM users WHERE active = true AND created_at > ${since} ORDER BY created_at DESC LIMIT ${limit} `; ```
No magic. No hidden N+1 queries. No wondering what SQL your ORM is actually generating.
The one thing ORMs genuinely do well is migrations. I replaced that with db-migrate for structured migrations and a simple `schema.sql` file as source of truth. Not glamorous, but it works.
- Greenfield CRUD-heavy apps where speed of development matters more than performance
- Teams where not everyone is comfortable with SQL
- Simple one-table queries where the ORM adds zero friction
For anything else? Write the SQL. You'll thank yourself later.