Raw Queries vs ORM
Part of the Backend Developer Roadmap
In short
ORMs are great for everyday CRUD, but some queries are easier, or only possible, in plain SQL. Good backend developers use both without guilt.
Knowing when to bypass your ORM and write raw SQL directly — usually for complex reporting queries or performance-critical paths the ORM handles poorly.

Think of it like this
Menu card vs custom order
At a dosa counter, pointing at the menu gets you a perfect masala dosa, fast. But for a paper-thin ghee roast folded your way, you tell the cook exactly how. The ORM is the menu; raw SQL is telling the cook precisely what to make.
Watch it happen
Which one, when?
ORM
- Typed results and autocomplete
- Safe parameters by default
- Easy relations and migrations
Raw SQL
- Full SQL power: CTEs, window functions
- You control the exact query
- You handle types and safety yourself
Use the ORM for most queries. Drop to raw SQL for reports and tricky queries, always with parameters, never string concatenation.
The big ideas
What you need to know
ORM for everyday work
Create, read, update, delete and simple relations are cleaner and safer through the ORM.
Raw SQL for the hard bits
Reports with window functions, CTEs or database-specific features are often clearer as SQL.
Parameters, always
Raw doesn't have to mean unsafe. Use tagged templates or placeholders, never string concatenation.
Escape hatches built in
Prisma has $queryRaw, TypeORM has query() and Knex has raw(). You don't have to leave your ORM.
See it in code
Walk through the code
1// Safe: Prisma's tagged template turns ${city} into a $1 parameter2const top = await prisma.$queryRaw<{ name: string; rank: bigint }[]>`3 SELECT name,4 RANK() OVER (ORDER BY total_spent DESC) AS rank5 FROM customers6 WHERE city = ${city}7 LIMIT 108`;9 10// Dangerous: string concatenation lets input become SQL11// await prisma.$queryRawUnsafe("SELECT * FROM users WHERE city = '" + city + "'");1/4$queryRaw is a tagged template. You write the SQL; Prisma handles the parameters.
Type the result yourself, as with the generic here. Postgres RANK() returns a bigint, so Prisma hands you a BigInt.
Save yourself the pain
Common mistakes
Oops
Building raw SQL by concatenating user input.
Do this instead
Always use placeholders or tagged templates. One stray quote in the input can otherwise rewrite your query.
Oops
Forcing a complex report through the ORM's query API until it's unreadable.
Do this instead
If the ORM version is twice as long as the SQL, write the SQL. Keep it in one well-named function.
Your mission
Leaderboard query
Quick check
Which of these raw queries is safe from SQL injection?
Go deeper
- 1Rewrite one ORM query as raw SQL and compare readability and performance.
- 2Learn your ORM's escape hatch for raw queries, e.g. Prisma's `$queryRaw`.
- 3Keep raw queries parameterized to avoid reintroducing SQL injection risk.
More topics at this stop
Stuck on this topic? Ask an Insider
Get 1:1 guidance from people who've walked this exact path — on the InsideEdge app.