This episode closes the most common security gaps: security headers with helmet, input validation and injection prevention, rate limiting to prevent brute-force attacks, secrets management, and dependency audits with npm audit.

Security isn't a feature added at the end — security is a decision made at every layer: HTTP headers, input validation, access restrictions, and secrets management. A Node.js application that neglects any of these becomes an easy target.
Episode 20 covers practical security hardening: security headers with helmet, injection prevention with validation and parameterized queries, rate limiting to hold back brute force, proper secrets management, and dependency audits. After this episode, your application is much harder to break into.
Helmet sets a collection of HTTP headers that neutralize many web attack techniques: XSS, clickjacking, MIME sniffing, and more. Installation is just one line:
npm install helmetnpm install helmet adds a package that configures security headers automatically according to industry recommendations.
import helmet from "helmet";
app.use(helmet());app.use(helmet()) sets headers like Content-Security-Policy, X-Frame-Options, X-Content-Type-Options, and Strict-Transport-Security. The Content-Security-Policy header restricts which script sources are allowed to run — a strong shield against XSS. If needed, configure specific policies through the option helmet({ contentSecurityPolicy: { directives: {...} } }).
Injection happens when untrusted data gets executed — SQL injection inserts commands into a query, and XSS injects scripts into a page. Two habits from episodes 15 and 14 already close the main path:
const hasil = skemaPengguna.safeParse(req.body);
if (!hasil.success) {
return res.status(400).json({ error: "Data tidak valid" });
}
const pengguna = await pool.query(
"SELECT * FROM pengguna WHERE email = $1",
[hasil.data.email],
);skemaPengguna.safeParse(req.body) validates the input shape, and the $1 placeholder with a parameter array ensures values are never interpreted as SQL commands. This combination — validation at the edge of the application and parameterized queries — closes the majority of injection gaps in Node.js applications.
Input used for file paths is another gap: req.params.nama from episode 17 could contain ../ to break out of a directory. Always sanitize with resolution and directory restriction before using input as a file name.
Brute force, scrapers, and DoS all rely on one thing: calling an endpoint endlessly. Rate limiting limits the number of requests from a single client within a time window:
npm install express-rate-limitnpm install express-rate-limit adds middleware that tracks requests per IP.
import rateLimit from "express-rate-limit";
const limiter = rateLimit({
windowMs: 15 * 60 * 1000,
limit: 100,
standardHeaders: true,
});
app.use("/api/login", limiter);The configuration above limits 100 requests per 15 minutes per IP on the login route. Excess responses automatically reply with 429 Too Many Requests. Mount a loose limiter on general APIs and a strict limiter on sensitive endpoints like login and password reset.
Secrets — JWT_SECRET, DATABASE_URL, API keys — must never enter code or a repository. The habit from episode 12 must be held: store them in environment variables, don't commit .env, and rotate leaked secrets. For advanced needs, consider secrets management tools like Vault or OpenBao, which store and release encrypted secrets.
A Node.js application contains hundreds of packages, and every package can carry a vulnerability. npm audit scans the dependency tree against a vulnerability database:
npm audit
npm audit fixnpm audit reports vulnerabilities with severity levels, while npm audit fix updates packages automatically when safe. Make this audit part of your routine: run it periodically and in CI, because new vulnerabilities are found every day. Unused packages are better removed — shrinking the attack surface.
No single key makes an application secure. Security works in layers: correct headers fend off browser attacks, validation and safe queries close injection, rate limiting holds back abuse, and guarded secrets limit the impact of a leak. The more layers, the more expensive an attack becomes for the attacker — and the harder your application is to break into.
Here's what to take away:
npm audit is scanned periodically and in CI for dependency vulnerabilities.In the next episode, episode 21, we'll discuss containerization, Docker, and simple deployment — a multi-stage Dockerfile for Node.js applications, .dockerignore, slim images, running containers with docker compose, and simple deployment strategies. You'll package the application to run the same way anywhere.