How SQL injection works and why parameterized queries prevent it
SQL injection mechanics, string concatenation vulnerability, parameterized queries, prepared statements, ORM safety, blind SQLi
SQL Injection
SQL injection is the most common critical web vulnerability. It happens when untrusted input is concatenated into a SQL query. The database cannot distinguish between your SQL and the attacker's SQL because they arrive as one string.
The Vulnerable Pattern
// VULNERABLE — never do this
const query = `SELECT * FROM users WHERE email = '${req.body.email}'`;
db.query(query);
// An attacker sends: ' OR '1'='1
// Resulting query: SELECT * FROM users WHERE email = '' OR '1'='1'
// Returns ALL usersThe Fix: Parameterized Queries
// SAFE — parameter is sent separately from SQL
const query = 'SELECT * FROM users WHERE email = ?';
db.query(query, [req.body.email]);
// With pg (PostgreSQL):
const result = await client.query(
'SELECT * FROM users WHERE email = $1',
[req.body.email]
);The key insight: with parameterized queries, user input is never interpreted as SQL. The database driver sends the SQL and the parameters separately. The parameter is treated as data, not executable code, regardless of what it contains.
What About ORMs?
Sequelize, Prisma, and TypeORM use parameterized queries internally—but only when you use their query-builder APIs. If you use sequelize.query(rawSql + userInput), you reintroduce the vulnerability. Always use the ORM's where-clause objects instead of raw string interpolation.
// SAFE — Sequelize where clause
User.findOne({ where: { email: req.body.email } });
// VULNERABLE — raw query with interpolation
sequelize.query(`SELECT * FROM users WHERE email = '${req.body.email}'`);