SQL injection happens when user input becomes part of the query rather than a value inside it. In Node the usual shape is a template literal.
Send ' OR '1'='1 as the email and the query returns every row. Send something worse and it does something worse.
The fix
Pass input as a parameter. The client sends the query and the values separately, so the database never parses the input as SQL.
node-postgres
mysql2
That's the whole fix. It's not slower, and on most drivers it's marginally faster because prepared statements get reused.
Escaping is not the fix
The instinct is to clean the input, strip quotes, blocklist keywords, run it through an escape function. This keeps reappearing and it keeps failing.
Blocklists fail because SQL has more ways to express things than anyone enumerates. Escape functions fail when the encoding or character set differs from what the function assumed. And any approach based on cleaning input has to be applied correctly at every single call site, forever, by everyone who joins the team. Parameterisation is one habit and it either happened or it didn't.
Two cases parameters don't cover
Identifiers. You cannot parameterise a table or column name.
Validate against an allowlist instead:
Raw ORM queries. Prisma, Sequelize, Knex and TypeORM parameterise their query builders by default, which is why teams using them rarely see this bug. The escape hatch is where it comes back.
Search your codebase for $queryRawUnsafe, sequelize.query, knex.raw and similar. That's where it will be if it's anywhere.
What tooling will and won't catch
Better than most classes, and not as well as you'd hope. We measured this in RealVuln, our benchmark of 26 scanners across 66 Python repositories with hand-labelled vulnerabilities. On SQL injection specifically, rule-based scanners recalled 37% of what was there. LLM-based scanners recalled 96%.
So a clean report from a rule-based tool means roughly a one-in-three chance each instance was caught, not that your codebase is clean. The pattern is distinctive enough that these tools do markedly better here than on authorisation bugs, where the vulnerable code and the correct code are indistinguishable. But 37% is not coverage.
Grep alongside the scan. The call sites are few enough to read.