
How to Prevent SQL Injection: Practical Security Coding Patterns in Node.js and Python
SQL Injection (SQLi) is one of the oldest and most dangerous vulnerabilities in web development. Despite being well understood for decades, it remains near the top of the OWASP Top 10 list of security risks.
A successful SQL injection attack can allow attackers to bypass login screens, read confidential database records (such as passwords and credit cards), modify or destroy data, and in some cases, execute operating system commands on the database server.
In this guide, we will analyze how SQL injection happens, understand why string concatenation is dangerous, and explore secure database querying patterns in Node.js and Python.
How SQL Injection Works
SQL injection occurs when user-supplied input is directly concatenated into a database query string instead of being handled as a separate parameter. This allows the input to alter the syntax of the SQL command.
Consider this insecure login query:
SELECT * FROM users WHERE username = 'USER_INPUT' AND password = 'PASSWORD_INPUT';If an attacker enters the following string into the username field:
admin' OR '1'='1The compiled query becomes:
SELECT * FROM users WHERE username = 'admin' OR '1'='1' AND password = 'PASSWORD_INPUT';Because '1'='1' is always true, the database executes the query, ignores the password check, and logs the attacker into the first account (typically the administrator).
Defense 1: Parameterized Queries (Prepared Statements)
The most effective way to prevent SQL injection is using Parameterized Queries (also known as Prepared Statements).
When you use parameterized queries, the database driver sends the query template and the user inputs separately. The database pre-compiles the SQL syntax template first, and then inserts the parameters strictly as raw data values, never executing them as SQL code.
Secure Pattern in Node.js (pg Driver)
// Insecure: Vulnerable to SQL Injection
const query = `SELECT * FROM users WHERE email = '${req.body.email}'`;
const result = await db.query(query);
// Secure: Parameterized Query
const query = 'SELECT * FROM users WHERE email = $1';
const values = [req.body.email];
const result = await db.query(query, values);Secure Pattern in Python (sqlite3)
# Insecure: Vulnerable to SQL Injection
query = f"SELECT * FROM users WHERE email = '{email}'"
cursor.execute(query)
# Secure: Parameterized Query
query = "SELECT * FROM users WHERE email = ?"
cursor.execute(query, (email,))Defense 2: Using ORMs and Query Builders
Object-Relational Mappers (ORMs) like Prisma, Drizzle, or SQLAlchemy prevent SQL injection by automatically compiling your queries using parameterized queries behind the scenes.
Using Prisma:
// Safely compiled to a parameterized query
const user = await prisma.user.findUnique({
where: { email: userInput },
});However, be careful. Most ORMs allow you to write raw SQL commands (e.g., prisma.$queryRaw). If you concatenate strings inside a raw query method, you introduce SQL injection vulnerabilities back into your application.
Defense 3: Principle of Least Privilege
Even if your code is secure, your database server configuration must act as a backup security layer. Apply the Principle of Least Privilege:
- Restrict Connection Permissions: The database user account used by your web application should only have permissions to read and write to specific tables.
- Block System Commands: Block the application database user from dropping tables, creating new database accounts, or accessing files on the operating system host disk.
- Disable Multiple Statements: Ensure your database driver is configured to reject multiple queries separated by semicolons in a single call (e.g., preventing
SELECT * FROM users; DROP TABLE users;).
Conclusion
Preventing SQL injection is straightforward: never concatenate user input into database query strings. By utilizing parameterized queries (Prepared Statements) for all raw SQL, leveraging modern ORMs, and enforcing the principle of least privilege on your database connections, you can secure your database assets from malicious input manipulation.