How do you prevent SQL injection?
By FrontendPro Editorial Team Updated 9/26/2026
Answer
Never build a query by concatenating strings. Use parameterised queries, where the SQL text and the values travel separately and the database never treats input as code.
// vulnerable — input becomes part of the statement
db.query(`SELECT * FROM users WHERE email = '${email}'`);
// email = "' OR '1'='1" returns every row
// safe — the driver sends the value out of band
db.query("SELECT * FROM users WHERE email = $1", [email]);
The second form is not "escaping done well"; the value is sent as data on the wire, so there is no parsing step for an attacker to influence. Every mainstream driver supports it — pg, mysql2, better-sqlite3 — and ORMs and query builders such as Prisma, Drizzle and Knex do it for you.
Where it still goes wrong
- Template literals in a raw escape hatch.
prisma.$queryRawUnsafe,knex.rawand friends bypass the protection. Use the tagged versions (prisma.$queryRaw...``) which parameterise interpolations. - Identifiers cannot be parameters. Table and column names, and
ORDER BYdirection, are not bindable. Validate them against an allowlist:
const SORTABLE = { name: "name", created: "created_at" };
const column = SORTABLE[req.query.sort] ?? "created_at";
- Dynamic
INclauses. Generate one placeholder per value rather than joining the array into the string. - NoSQL is not immune. Passing a parsed JSON body straight into a MongoDB filter lets
{"$ne": null}through as an operator. Validate types before querying.
Defence in depth
Parameterisation is the control that actually stops the attack; the rest limits the damage. Validate input shape with a schema library, give the application database user only the privileges it needs — no DROP, no access to unrelated tables — and return generic error messages, since a leaked SQL error tells an attacker your schema.
