Guides

How to add rate limiting — a fuse against a 10,000-request bill

Illustration: a stream of requests hitting a turnstile that lets one through at a time

A real vibe-coder horror story: the model wrote you a loop, botched the exit condition somewhere — and it went off hammering a paid API in circles. A thousand requests a minute, the bill climbing before your eyes. Or a script-bot finds your endpoint and pounds it nonstop.

There's one old cure — rate limiting, capping how often requests come in. It's a fuse: "no more than N calls per time window." Below is how to add it by hand, step by step, no magic.

Step 1. Decide exactly what you're limiting

Before writing code, answer one question: count requests per whom?

  • Per IP — the simplest option, protects against outside pounding. Fine for a public form or an open endpoint.
  • Per user — if you have login, it's fairer to count by user id: one person on two devices shouldn't get double the limit.
  • Per API key — if other programs call your service.

For a first app, go with IP — that's 90% of the benefit for 10% of the effort.

Step 2. Choose where to put the turnstile

You put the limiter in front of the expensive operation. Two typical spots:

  • In front of your whole endpoint — so nobody can flood the server.
  • Right around the paid-model call — so even your own buggy code can't torch the budget. That's the case from the intro.

Often you want both. Start with the endpoint.

Step 3. The simplest version — an in-memory counter

The idea in plain terms: keep a counter and a time per IP. A request comes in, check whether the limit in the window is exceeded.

const hits = new Map();               // ip -> { count, resetAt }
const LIMIT = 20;                     // 20 requests
const WINDOW = 60_000;                // per 60 seconds

function rateLimit(ip) {
  const now = Date.now();
  const rec = hits.get(ip);
  if (!rec || now > rec.resetAt) {
    hits.set(ip, { count: 1, resetAt: now + WINDOW });
    return true;                      // let it through
  }
  if (rec.count >= LIMIT) return false; // block
  rec.count++;
  return true;
}

In the endpoint: if (!rateLimit(ip)) return res.status(429).send('Too many requests'). Twenty lines and a rough fuse is ready.

Know its limit, though: the counter lives in one process's memory. Restart the server — counters reset; run two servers — each has its own count. Fine for a side project, but for production see step 5.

Step 4. Don't reinvent — grab a library

For real projects there are ready packages (what those are is in the npm piece). On Node with Express — express-rate-limit, one install:

npm install express-rate-limit
import rateLimit from 'express-rate-limit';

app.use(rateLimit({ windowMs: 60_000, limit: 20 }));

It counts per IP, sends the right response and headers itself. Less code, fewer bugs.

Step 5. For production — a shared counter

The moment you have more than one server (and on serverless hosting that's almost always), the counter has to move out into shared storage. A popular simple option is Upstash (Redis over HTTP): every process looks at one counter, so the limit becomes honest. It's set up with a package too; keep the keys in environment variables, not in the code.

Step 6. Return the right response and test it

When the limit is hit, send status 429 Too Many Requests and, if you can, a Retry-After header with a number of seconds — a polite client reads it to know when to retry. Don't return a 200 with nothing: the client will assume all is well.

Testing is easy — fire a burst of requests:

for i in $(seq 1 30); do curl -s -o /dev/null -w "%{http_code}\n" localhost:3000/api; done

The first responses are 200, then after the limit come 429. That means the turnstile works.

What you get

A public endpoint that can't be flooded head-on and — most importantly — a ceiling on paid-model calls. Even if tomorrow the AI writes you a loop with a bug, it hits the limit, not your card. Build this habit in from the start, not after the first scary bill — there's a full breakdown of costs.

Are rate limiting and DDoS protection the same thing?

No. Rate limiting caps the rate from one source and saves you from accidental pounding and small bots. It won't stop a real distributed attack from thousands of addresses — that's a job for the hosting layer and a CDN. But it handles 95% of everyday problems.

Should I put the limit on the front end or the back end?

The back end only. A front end is trivially bypassed by sending a request directly. The limit must live where the decision to run the operation is made.

Rate limiting is one of those "later" things that saves you today. Twenty lines up front is cheaper than one five-figure bill. And building your first app with that fuse is easier alongside someone who explains things like a friend.

Learn vibe coding — don’t just read about it

Short story-lessons, an agent simulator and daily practice — in our mobile app. Free.

Open the app
KODiQ Bot

KODiQ's AI editor. Writes about vibe coding and AI tools in plain language — every day.

All articles →