# Node.js Security Essentials
## Rule
All Node.js applications MUST validate input with a schema library, set security headers, never store secrets in code, and run regular dependency audits. No exceptions for internal services.
## Security Checklist
| Area | Requirement | Tool |
|------|-------------|------|
| Input validation | Schema validation on all endpoints | zod, joi |
| HTTP headers | Security headers on all responses | helmet |
| Rate limiting | Rate limit all public endpoints | express-rate-limit |
| Dependencies | Regular audit for vulnerabilities | npm audit, snyk |
| Secrets | Never in code, use env vars | dotenv (dev only) |
| SQL injection | Parameterized queries only | Prepared statements |
## Good Examples
```javascript
import helmet from "helmet";
import rateLimit from "express-rate-limit";
import { z } from "zod";
// Security headers
app.use(helmet());
// Rate limiting
app.use("/api/", rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
standardHeaders: true,
}));
// Input validation with Zod
const createUserSchema = z.object({
email: z.string().email().max(255),
name: z.string().min(2).max(100),
password: z.string().min(8).max(72),
});
app.post("/users", async (req, res) => {
const data = createUserSchema.parse(req.body);
// data is validated and typed
});
// Parameterized queries (never string concatenation)
const user = await db.query(
"SELECT * FROM users WHERE id = $1",
[req.params.id]
);
```
## Bad Examples
```javascript
// BAD: No input validation
app.post("/users", async (req, res) => {
await db.query(`INSERT INTO users (name) VALUES ('${req.body.name}')`);
// SQL injection vulnerability!
});
// BAD: Secrets in code
const API_KEY = "sk_live_abc123def456";
// BAD: No security headers
app.listen(3000); // No helmet, no rate limiting
```
## Enforcement
- `npm audit` in CI — fail on high/critical vulnerabilities
- ESLint security plugins: eslint-plugin-security
- Snyk or Socket.dev for dependency monitoring
- Mandatory helmet and rate-limit middleware