SQL Injection in Express.js Applications
Express.js does not include a database layer, so SQL injection risk depends entirely on how developers query databases. Raw SQL with string concatenation, improper use of query builders, and unparameterized queries in pg, mysql2, or knex are the primary attack vectors.
Scan Your Express AppHow SQL Injection Manifests in Express
Express apps commonly use database libraries like pg (PostgreSQL), mysql2, or better-sqlite3 directly. SQL injection occurs when user input from req.query, req.params, or req.body is concatenated into SQL strings. Even when using query builders like Knex.js, developers sometimes use knex.raw() with string interpolation instead of parameter bindings. Sequelize's literal() and query() methods have the same risk. Dynamic table names, column names, and ORDER BY clauses are particularly dangerous because they cannot use parameterized queries and require explicit allowlist validation.
Real-World Impact
An Express API used string concatenation to build a search query from user input. An attacker discovered that the /api/users?search= endpoint was vulnerable and extracted the entire users table including hashed passwords and email addresses using UNION-based SQL injection. The breach exposed 50,000 user records.
Step-by-Step Fix
Use parameterized queries
Replace string concatenation with parameterized queries in your database driver.
// UNSAFE - string concatenation
const result = await pool.query(
`SELECT * FROM users WHERE email = '${req.query.email}'`
);
// SAFE - parameterized query (pg)
const result = await pool.query(
'SELECT * FROM users WHERE email = $1',
[req.query.email]
);
// SAFE - parameterized query (mysql2)
const [rows] = await pool.execute(
'SELECT * FROM users WHERE email = ?',
[req.query.email]
);Use an ORM with parameterized queries
ORMs like Prisma and Drizzle handle parameterization automatically.
// Prisma - automatically parameterized
const user = await prisma.user.findUnique({
where: { email: req.query.email as string },
});
// Drizzle - automatically parameterized
const user = await db
.select()
.from(users)
.where(eq(users.email, req.query.email as string));Allowlist dynamic identifiers
Column names and sort orders cannot be parameterized. Use an allowlist instead.
const ALLOWED_SORT_COLUMNS = ['name', 'created_at', 'email'];
const ALLOWED_DIRECTIONS = ['asc', 'desc'];
app.get('/api/users', (req, res) => {
const sortBy = ALLOWED_SORT_COLUMNS.includes(req.query.sort as string)
? req.query.sort : 'created_at';
const dir = ALLOWED_DIRECTIONS.includes(req.query.dir as string)
? req.query.dir : 'asc';
const result = await pool.query(
`SELECT * FROM users ORDER BY ${sortBy} ${dir}`
);
});Use knex.raw() safely
When using Knex raw queries, always use parameter bindings.
// UNSAFE - string interpolation in raw()
const result = await knex.raw(
`SELECT * FROM users WHERE name LIKE '%${search}%'`
);
// SAFE - parameter binding in raw()
const result = await knex.raw(
'SELECT * FROM users WHERE name LIKE ?',
[`%${search}%`]
);Prevention Best Practices
1. Always use parameterized queries or prepared statements. 2. Use an ORM like Prisma, Drizzle, or Sequelize with proper parameter binding. 3. Validate and allowlist dynamic column names and sort orders. 4. Never concatenate user input into SQL strings. 5. Use database accounts with minimal required permissions.
How to Test
1. Test input fields with ' OR 1=1 -- to check for basic SQL injection. 2. Search for string concatenation in SQL: grep -r "\$\{.*\}" --include="*.ts" | grep -i "select\|insert\|update\|delete" 3. Check for knex.raw(), sequelize.query(), and pool.query() with template literals. 4. Use sqlmap against your API endpoints for automated testing. 5. Use Vibe App Scanner to detect SQL injection patterns in your Express application.
Frequently Asked Questions
Does using an ORM prevent SQL injection in Express?
ORMs like Prisma and Drizzle parameterize queries automatically, which prevents most SQL injection. However, raw query methods (prisma.$queryRaw, knex.raw()) can still be vulnerable if you use string concatenation. Always use parameter bindings even in raw queries.
Can NoSQL databases get SQL injection in Express?
NoSQL databases like MongoDB are not vulnerable to SQL injection, but they have their own injection attacks (NoSQL injection). MongoDB queries using $where clauses or building query objects from user input can be exploited. Always validate and sanitize input regardless of database type.
Is input validation enough to prevent SQL injection?
No. Input validation is a defense-in-depth measure but should not be the primary defense. Always use parameterized queries as the first line of defense. Attackers can bypass validation with encoding tricks, but parameterized queries are fundamentally immune to injection.
Related Security Resources
Is Your Express App Vulnerable to SQL Injection?
VAS automatically scans for sql injection vulnerabilities in Express applications and provides step-by-step remediation guidance with code examples.
Scans from $5, results in minutes. Get actionable fixes tailored to your Express stack.