SQL injection (SQLi) has sat at or near the top of web vulnerability lists for over two decades. It is conceptually simple, devastating in impact, and completely preventable with patterns you can adopt today. This lesson explains how the flaw arises and drills the defensive habits that eliminate it.
SQL injection happens when untrusted input is concatenated into a SQL statement as text, letting the input change the structure of the query instead of acting as a value. Consider a login check built by string concatenation:
// VULNERABLE PATTERN - never do this
const query = "SELECT * FROM users WHERE email = '" + email + "' AND password = '" + password + "'";The developer intended email to be data. But because it is pasted into the command text, specially crafted input can terminate the string and append its own SQL clauses. The database cannot tell the developer's SQL from the attacker's, because they arrive as one string. Consequences of successful injection include reading entire tables, bypassing authentication, modifying or deleting data, and in some configurations executing commands on the server.
The exact same root cause, trusting input to remain data, underlies XSS, command injection and LDAP injection. Learn the shape once and you will recognize it everywhere.
Parameterized queries (prepared statements) send the SQL command and the data through separate channels. The query structure is fixed first; values are bound after and can never be interpreted as SQL, no matter what they contain.
// SAFE: parameterized query (node-postgres)
const result = await pool.query(
'SELECT id, name FROM users WHERE email = $1 AND active = $2',
[email, true] // values bound separately, always treated as data
);
// SAFE: parameterized query (Python + sqlite3)
// cursor.execute("SELECT id FROM users WHERE email = ?", (email,))Every mainstream database driver and language supports this. There is no performance penalty; prepared statements are often faster because the database can cache the plan. Make parameterization a reflex: if you ever see string concatenation building SQL, treat it as a bug regardless of where the input comes from.
ORMs such as Prisma, Sequelize, Hibernate and Django ORM parameterize for you in normal usage, which is a strong reason to use them. Stay alert for their raw-query escape hatches ($queryRawUnsafe, extra(), string-built whereRaw), which reintroduce the risk if you concatenate input into them. When raw SQL is genuinely needed, use the parameterized raw variants.
Parameterization is the cure, but layered controls limit damage if a mistake slips through:
^[0-9]+$); this narrows the attack surface and improves data quality.Include tests that feed hostile-looking strings (quotes, semicolons, SQL keywords) into every input and assert they are stored and retrieved literally, not interpreted. Static analysis tools and linters can flag string-concatenated queries in code review. Dependency scanners matter too, since injection bugs are regularly found in libraries.
Next lesson: XSS: Cross-Site Scripting and Defenses, the injection attack that targets your users' browsers.