Skip to the solution
HardSystem Design6 min read

How do you secure your Node.js API endpoints?

384 views

The problem

Question presented to candidate: "An API endpoint accepts a JSON body and returns data from a database. Walk through every layer of security that should exist between the raw incoming request and your business logic actually running."

What a strong answer should cover:

  • Securing an API endpoint is a layered problem, not one control: authentication (who is making this request), authorization (are they allowed to do this specific thing), input validation (is the request body/params/query actually well-formed and safe), rate limiting (is this client making requests at an acceptable rate), and transport/header-level protections (HTTPS, security headers) β€” each addressing a genuinely different failure mode.
  • Authentication commonly uses a JWT or session-based token, verified on every request β€” the dedicated JWT-vs-session-based-authentication question covers the specific trade-offs between the two approaches and where refresh tokens fit.
  • Input validation with a schema library (zod/joi, covered in its own dedicated question) should run before any business logic touches the request body β€” rejecting a malformed or unexpected request shape immediately, rather than letting invalid data reach deeper code paths where it could enable SQL injection, prototype pollution, or other injection classes (each covered with a real demonstrated exploit in their own dedicated questions).
  • πŸ“Œ Verified, not assumed: HTTP security headers via helmet() add real, checkable protection with a single middleware call β€” confirmed directly: a server without it exposed only x-powered-by: Express; with it, a full protective header set (CSP, HSTS, X-Frame-Options, and others) appeared automatically.
  • Rate limiting (covered fully in its own dedicated question) protects against both brute-force credential attacks and basic denial-of-service, and should apply per-identity (per API key/user, not just per IP) where the endpoint is authenticated, since a single IP is not a reliable proxy for a single real client at scale.
  • CORS and CSRF (both covered in their own dedicated questions, with real tested browser behavior for CSRF specifically) address genuinely different threats from the layers above β€” a complete answer names them as distinct, not folds them into "authentication" vaguely.
  • A precise answer treats endpoint security as this specific ordered pipeline β€” headers/transport, rate limiting, authentication, authorization, input validation, then business logic β€” rather than a flat, unordered list of good ideas.

Clarifying questions expected:

  • "Is this endpoint authenticated at all, or intentionally public?" β€” decides how much of the pipeline (auth, per-identity rate limiting) actually applies.
  • "Is the concern a specific layer (auth, input validation) or the complete pipeline end to end?"

Code / implementation expected: Yes β€” the real, verified helmet() header difference grounds the headers layer concretely; the rest of the pipeline cross-links to its own dedicated, individually-verified question.

  • security
  • api
  • authentication
  • authorization

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

6 min read Β· 9 sections

Read the code

JavaScriptA real header comparison confirming helmet's protective effect on an Express endpoint
const express = require("express");
const helmet = require("helmet");

const withoutHelmet = express();
withoutHelmet.get("/", (req, res) => res.json({ ok: true }));

const withHelmet = express();
withHelmet.use(helmet());
withHelmet.get("/", (req, res) => res.json({ ok: true }));

// WITHOUT helmet: [["x-powered-by","Express"]]
// WITH helmet:    [["content-security-policy","..."], ["strict-transport-security","..."],
//                  ["x-content-type-options","nosniff"], ["x-frame-options","SAMEORIGIN"], ...]
// -- a single middleware call, a real, checkable difference

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 143 of 152 in Node.js

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Node.js questions
Up nextHard

How do you build a worker-thread pool (e.g. Piscina) for CPU-bound work?

Continue