Going ProfessionalPro· 40 min read

Securing Your API (Hardening)

A public API is a target the moment it goes live. Add the standard layers of protection every professional back-end ships with.

What you will learn

  • Add secure HTTP headers with Helmet
  • Throttle abusive traffic with rate limiting
  • Configure CORS tightly and sanitize input against injection

Your API lives in a rough neighbourhood

The instant your API is reachable on the internet, automated bots start probing it — trying common attacks, hammering login routes, and looking for weak spots. You cannot stop them knocking, but you can make the door hard to break. Professional back-ends ship with several layers of defence, each cheap to add and each closing off a class of attack. We will add the four most important.

Layer 1 — secure headers with Helmet

Browsers respect special HTTP response headers that tighten security — telling the browser not to guess content types, not to embed your site in a hostile frame, and so on. Setting them by hand is fiddly, so the Helmet package sets a sensible bundle of them in one line:

Terminal: install Helmet
npm install helmet

Note: Output: added 1 package in 1s This downloads the helmet package into node_modules/ and records it in package.json, so you can require it in the next step.

Enable secure headers
const helmet = require("helmet");
app.use(helmet());   // sets ~15 protective headers automatically

Note: Output: (no visible output — headers are added to every response) app.use(helmet()) runs on every request and attaches a set of well-tested security headers. There is nothing to configure to get started — it is one of the highest-value single lines you can add to an Express app.

Layer 2 — rate limiting (stop the spammers)

Without limits, a bot can fire thousands of requests per second — guessing passwords on your login route or simply overwhelming your server (a denial-of-service attack). Rate limiting caps how many requests one client (by IP address) may make in a time window, replying 429 (Too Many Requests) once they go over.

Throttle requests per IP
const rateLimit = require("express-rate-limit");

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000,   // 15 minutes
  max: 100,                   // 100 requests per IP per window
  message: { error: "Too many requests, please try again later" }
});

app.use(limiter);             // apply to all routes

How it reads: windowMs is the length of the window (here 15 minutes, written as 15 * 60 * 1000 milliseconds), and max is how many requests one IP gets in that window. The 101st request inside 15 minutes is rejected with a 429 and your message, then the counter resets when the window rolls over.

Note: Output (the 101st request within 15 minutes from one IP): {"error":"Too many requests, please try again later"} Real apps often apply a stricter limiter just to sensitive routes like /login (say, 5 attempts per 15 minutes) to blunt password-guessing, plus a looser global one for everything else.

Layer 3 — CORS, done properly

In the earlier lesson you enabled CORS with app.use(cors()) — which allows requests from any origin. That is convenient for learning but means any website on the internet can call your API from a browser. In production you whitelist only your own front-end:

Lock CORS to your real front-end
const cors = require("cors");

app.use(cors({
  origin: "https://myapp.com",   // only allow YOUR front-end
  credentials: true              // allow cookies to be sent
}));

Note: Output: (no visible output — it changes which origins the browser permits) Now a browser will only let your own site at https://myapp.com call the API; a request from a random origin is blocked by the browser. credentials: true is needed if you send auth cookies along with requests. (Note: CORS is a browser rule, so it is one layer among several — not a complete shield, which is why we also add the others.)

Layer 4 — sanitize input against injection

An injection attack is when a user sneaks special characters into input hoping your code or database will execute them — for example MongoDB operators like $gt smuggled into a login form to trick a query, or <script> tags (an XSS attack — Cross-Site Scripting) meant to run in another user’s browser. Sanitizing strips or neutralises these before they do harm.

Strip injection attempts from input
const mongoSanitize = require("express-mongo-sanitize");
app.use(mongoSanitize());   // strip $ and . from request data

// Always escape user content before showing it in HTML, e.g.
// "<script>" becomes "&lt;script&gt;" so the browser shows it, not runs it.

Note: Output: (no visible output — malicious keys are removed from req.body/req.query) express-mongo-sanitize removes the $ and . characters that MongoDB query operators rely on, so an attacker cannot inject { "$gt": "" } to bypass a check. For XSS, the rule is: never trust user text in a page — escape it (or let your front-end framework escape it, as React does by default).

The hardening checklist

  1. helmet() — secure HTTP headers on every response.
  2. express-rate-limit — cap requests per IP (stricter on /login).
  3. Tight CORS — allow only your real front-end origin.
  4. Sanitize/validate all input (this plus the validation lesson).
  5. Keep secrets in .env, run over HTTPS in production, and never leak raw error details to clients.

Tip: None of these is a silver bullet — security is layers. Each one closes a door; together they make your API a hard target. The best part is most are a single app.use(...) line, so there is no excuse to skip them.

Q. What does rate limiting protect against?

Answer: Rate limiting caps how many requests one IP can make in a time window, returning 429 when exceeded — blunting brute-force login attempts and denial-of-service floods.

✍️ Practice

  1. Add helmet() and a global express-rate-limit to an API, then spam a route until you receive a 429.
  2. Lock CORS to a single origin and confirm a request from a different origin is blocked.

🏠 Homework

  1. Harden your CRUD API with Helmet, a stricter rate limit on a /login route, tight CORS, and input sanitization — then write a short note on what each layer defends against.
Want to learn this with a mentor?

CodingClave runs guided, project-based training (28-day, 45-day & 6-month batches).

Explore Training →