Security Best Practices

Securing a Node API means layering defenses — secure HTTP headers with Helmet, controlled cross-origin access with CORS, rate limiting, input validation, and keeping secrets out of your code — so common attacks are blocked before they reach your logic.

Learn Security Best Practices in our free Node.js course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick…

Part of the free Node.js course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

In this lesson you'll see what Helmet's headers do, how to allowlist origins with CORS, how rate limiting throttles abuse, why you must validate and sanitize input, how to keep secrets in environment variables, and a tour of the OWASP basics that tie it all together.

What You'll Learn in This Lesson

1️⃣ Secure Headers (Helmet)

Browsers honour special HTTP response headers that harden a site. Helmet sets a sensible bundle of them in one line. Key ones: X-Content-Type-Options: nosniff stops MIME-sniffing, X-Frame-Options: DENY blocks clickjacking, Strict-Transport-Security forces HTTPS, and a Content-Security-Policy restricts where resources may load from. The model below builds that same header set.

2️⃣ Helmet, CORS & Rate Limiting in Express

In a real app these are three middlewares you add early. helmet() sets the headers above; cors() controls which origins may call your API — always allowlist specific origins instead of '*' ; and express-rate-limit caps requests per IP so brute-force and abuse hit a 429 . Capping the JSON body size adds one more guard.

3️⃣ How Rate Limiting Works

Rate limiting is easiest to understand as a token bucket : each client starts with N tokens, every request spends one, and when the bucket is empty further requests are rejected with 429 Too Many Requests until it refills. The runnable model below gives each client 3 tokens, so the 4th request onward is blocked — exactly what express-rate-limit does per IP.

Your turn. The sanitizer below works once you fill in the single blank marked ___ . Follow the 👉 hint, then run it.

No blanks — just a brief and an outline. Write input validation that rejects a bad email and a too-short password with clear error messages. Build it, run it, and compare with the example output.

📋 Quick Reference — Security Layers

Practice quiz

What does the Helmet middleware do?

  • Encrypts the database
  • Sets secure HTTP response headers
  • Rate limits requests
  • Validates input schemas

Answer: Sets secure HTTP response headers. Helmet sets a bundle of security-related response headers in one line.

Which header value stops browsers from MIME-sniffing responses?

  • X-Content-Type-Options: nosniff
  • X-Frame-Options: DENY
  • Strict-Transport-Security
  • Content-Security-Policy

Answer: X-Content-Type-Options: nosniff. X-Content-Type-Options: nosniff tells browsers not to guess the MIME type.

Why shouldn't you set CORS to allow all origins ('*') on an authenticated API?

  • It is slower
  • It breaks JSON parsing
  • Any site could trigger requests on behalf of a logged-in user
  • It disables HTTPS

Answer: Any site could trigger requests on behalf of a logged-in user. A wildcard origin lets any website call your API, dangerous for cookie/session-based auth.

What HTTP status does a rate limiter return once the cap is exceeded?

  • 403 Forbidden
  • 401 Unauthorized
  • 500 Server Error
  • 429 Too Many Requests

Answer: 429 Too Many Requests. Rate limiting returns 429 Too Many Requests when a client exceeds its allowance.

Where should secrets like API keys and DB passwords live?

  • In environment variables, not in source code
  • Hardcoded in the main file
  • In a public README
  • In the git commit message

Answer: In environment variables, not in source code. Load secrets from process.env (e.g. a gitignored .env) so they never get committed.

What attack does rate limiting most directly help blunt?

  • MIME sniffing
  • Brute-force password guessing
  • Clickjacking
  • CSS injection

Answer: Brute-force password guessing. Capping requests per client limits how many password attempts an attacker can make.

Which approach prevents classic SQL injection?

  • String concatenation of inputs
  • Logging the query
  • Parameterized queries or an ORM that escapes inputs
  • Disabling the database

Answer: Parameterized queries or an ORM that escapes inputs. Parameterized queries keep user input as data, never as executable SQL.

What does the express-rate-limit 'max' option set?

  • The response timeout
  • The body size limit
  • The number of CORS origins
  • The number of requests per IP per window

Answer: The number of requests per IP per window. max caps how many requests a single IP may make within windowMs before getting a 429.

What should you do with untrusted client input before storing or rendering it?

  • Validate and sanitize it
  • Trust it for speed
  • Email it to yourself
  • Cache it in Redis

Answer: Validate and sanitize it. Always validate types and shape and sanitize input to defend against injection and XSS.

What does the X-Frame-Options: DENY header protect against?

  • SQL injection
  • Clickjacking via iframes
  • Slow database queries
  • Expired TLS certificates

Answer: Clickjacking via iframes. Denying framing prevents your pages being embedded in a malicious iframe (clickjacking).