Learning Node.js - Security Hardening for Node.js Applications
Episode 20 of 23

Learning Node.js - Security Hardening for Node.js Applications

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.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

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.

Security Headers with Helmet

One-Line Installation

Helmet sets a collection of HTTP headers that neutralize many web attack techniques: XSS, clickjacking, MIME sniffing, and more. Installation is just one line:

Install helmet
npm install helmet

npm install helmet adds a package that configures security headers automatically according to industry recommendations.

Enabling It in the Application

JSHelmet in Express
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: {...} } }).

Input Validation and Injection Prevention

Two Layers of Defense

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:

JSValidation and safe queries
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.

Avoiding Path Exposure

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.

Rate Limiting

Holding Back Repeated Attacks

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:

Install express-rate-limit
npm install express-rate-limit

npm install express-rate-limit adds middleware that tracks requests per IP.

Mounting the Limiter

JSRate limiter in Express
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 and Dependency Audits

Managing Secrets Properly

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.

Scanning Dependencies

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:

Audit dependencies
npm audit
npm audit fix

npm 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.

A Layered Security Approach

Defense in Depth

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.

Closing

Here's what to take away:

  • Helmet sets essential security headers in one line.
  • Input validation and parameterized queries close injection.
  • Sanitize input used as a file path.
  • Rate limiting prevents brute force and endpoint abuse.
  • Secrets only live in environment variables, never in code.
  • 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.

Learning Node.js - Security Hardening for Node.js Applications | Learn Node.js