Skip to content
All articles
Technical7 May 2026

Parameterized SQL, and why no ORM

By Miroslav Tadej

There is a fashion in web development for hiding the database behind an ORM and never writing SQL again. It is comfortable, and for a lot of apps it is fine. But "comfortable" and "secure" are not the same word — and the single most damaging web vulnerability of the last two decades is still, in 2026, SQL injection.

How injection actually happens

Injection is not exotic. It is what happens the moment you build a query by gluing strings together:

// NEVER do this
const sql = `SELECT * FROM users WHERE email = '${email}'`;

Feed that an email of ' OR '1'='1 and the WHERE clause is always true. The query returns every user. Feed it '; DROP TABLE users; -- and you can guess the rest. The database did exactly what it was told — the problem is the application told it the wrong thing, because user input and code got mixed into the same string.

The fix is boring and total

Parameterized queries send the SQL and the values to the database separately. The values are never parsed as SQL, so they can never become SQL:

// the value is bound, never interpreted
const { rows } = await query(
  'SELECT * FROM users WHERE email = $1',
  [email],
);

$1 is a placeholder; [email] is data. There is no string in which an attacker's payload could change the query's meaning. This is not a mitigation or a filter you can get almost right — it closes the class of bug entirely. Every query in this codebase, without exception, looks like this.

So why skip the ORM?

ORMs can parameterize correctly, and good ones do. But there are reasons to keep the data layer in plain SQL on a project where you control the standards:

  • No hidden queries. You can see exactly what hits the database, and reason about its cost. ORMs make it easy to fire a hundred queries without noticing.
  • No leaky abstraction. The moment you need a window function, a CTE or a careful index hint, you are writing SQL anyway — through a string-builder that fights you.
  • One obvious rule to audit. "Every query uses $1, $2 placeholders; zero string concatenation, ever" is a rule a reviewer can verify by reading. "The ORM is configured safely everywhere" is not.

The point is not that ORMs are bad. It is that a small, explicit data layer with a single non-negotiable rule is easy to keep correct — and keeping it correct is the whole job.

Try it yourself

The Security Lab has a live SQL-injection demo: flip the same attack between a string-built query and a parameterized one, and watch the admin row leak in the first case and vanish in the second.

Book a consultation

General information, not professional advice — see our legal notice.