What are Security Best Practices in Node.js?
Node.js security best practices: input validation, parameterised queries, Helmet headers, secret management, password hashing and rate limiting explained.
Expected Interview Answer
Security best practices in Node.js are the habits that protect an app from common attacks: validating input, avoiding injection, managing dependencies, securing secrets and headers, and enforcing proper authentication and authorization.
Concretely this means validating and sanitising all input to prevent SQL/NoSQL and command injection, using parameterised queries, setting secure HTTP headers (often via Helmet), keeping dependencies patched with npm audit, storing secrets in environment variables rather than code, hashing passwords with bcrypt/argon2, enforcing HTTPS and secure cookies, applying rate limiting, and running the process with least privilege. Defence in depth — layering several of these — is what makes an app resilient rather than relying on any single control.
- Prevents injection, XSS and CSRF attacks
- Protects user credentials and sensitive data
- Reduces the blast radius of a compromise
- Keeps dependencies free of known vulnerabilities
- Builds user trust and supports compliance
AI Mentor Explanation
A well-run ground checks every ticket at the gate, frisks for prohibited items, and never lets an unverified person onto the pitch. Node.js security is that layered gate control: input is validated at entry, requests are authenticated, and permissions are checked before anyone touches the data, so a forged ticket or hidden weapon is stopped long before it reaches the wicket.
Step-by-Step Explanation
Step 1
Validate and sanitise input
Never trust client data; validate types and shapes (e.g. with a schema library) and sanitise before use to block injection and XSS.
Step 2
Use parameterised queries
Pass user values as parameters, never string-concatenate them into SQL/NoSQL queries, to prevent injection.
Step 3
Secure headers and transport
Apply Helmet for safe HTTP headers, enforce HTTPS, and set HttpOnly, Secure and SameSite cookies.
Step 4
Manage secrets and dependencies
Keep secrets in environment variables, and run npm audit to patch vulnerable packages regularly.
Step 5
Authenticate and authorize
Verify identity, hash passwords with bcrypt/argon2, and check permissions on every protected action.
Step 6
Rate limit and monitor
Throttle requests to stop brute-force and DoS abuse, and log and monitor for suspicious activity.
What Interviewer Expects
- Awareness of injection and how parameterised queries prevent it
- Knowledge of secure headers and Helmet
- Proper secret management via environment variables
- Password hashing with bcrypt or argon2, never plain text
- Rate limiting and dependency auditing (npm audit)
Common Mistakes
- Building SQL queries by concatenating user input
- Storing secrets or API keys directly in source code
- Storing passwords in plain text or with weak hashing like MD5
- Trusting client-side validation as the only check
- Ignoring npm audit warnings and running outdated dependencies
Best Answer (HR Friendly)
“Security best practices in Node.js are the everyday precautions that keep an app safe — checking all incoming data, hiding secrets, keeping libraries updated, and making sure only the right people can access the right things. Layering these protections means one mistake will not expose everything.”
Code Example
import express from 'express'
import helmet from 'helmet'
import rateLimit from 'express-rate-limit'
const app = express()
// Set safe security headers (CSP, HSTS, no-sniff, etc.)
app.use(helmet())
app.use(express.json({ limit: '10kb' }))
// Throttle abusive clients: 100 requests per 15 minutes per IP
app.use(
rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
standardHeaders: true,
}),
)
app.listen(3000)import bcrypt from 'bcrypt'
// Never concatenate user input into the SQL string
async function findUser(email) {
// Placeholders ($1) keep input as data, not executable SQL
const { rows } = await db.query(
'SELECT id, password_hash FROM users WHERE email = $1',
[email],
)
return rows[0]
}
async function register(email, password) {
const hash = await bcrypt.hash(password, 12)
await db.query(
'INSERT INTO users (email, password_hash) VALUES ($1, $2)',
[email, hash],
)
}Follow-up Questions
- How does a parameterised query prevent SQL injection?
- Why hash passwords with bcrypt instead of encrypting them?
- What does the Helmet middleware protect against?
- How do HttpOnly and SameSite cookie flags reduce risk?
- How would you defend against a brute-force login attack?
MCQ Practice
1. What is the main defence against SQL injection in Node.js?
Parameterised queries send user input as data bound to placeholders, so it can never be interpreted as executable SQL.
2. Where should API keys and database passwords be stored?
Secrets belong in environment variables or a dedicated secrets manager, never committed into source code where they can leak.
3. Why should passwords be hashed with bcrypt or argon2 rather than encrypted?
Password hashes are one-way and deliberately slow with a per-user salt, so even a database leak does not reveal the original passwords easily.
Flash Cards
Parameterised query — A query where user values are bound to placeholders instead of concatenated into the SQL string, preventing injection.
Helmet — Express middleware that sets protective HTTP headers (CSP, HSTS, X-Content-Type-Options) to mitigate common web attacks.
Secret management — Keeping API keys and passwords in environment variables or a secrets manager, never hardcoded in source control.
Password hashing — Storing passwords as one-way, salted, slow hashes (bcrypt/argon2) so a database leak does not expose plaintext passwords.
Rate limiting — Capping requests per client over a time window to blunt brute-force, credential-stuffing and denial-of-service attacks.