Blog
4 min read

What Is Rate Limiting? Protecting Your App From Too Many Requests

Rate limiting caps how many requests someone can make in a period. Why every public app needs it — for login forms, sign-ups, AI features, and APIs — how it works, what a 429 response means, where to add limits, and what to do when you hit someone else's.

Your login form will be attacked by bots trying thousands of passwords. Your sign-up form will be spammed. Your AI feature will be used by someone as a free AI service at your expense. None of this is personal — automated scripts do it to every public app. Rate limiting is one of the simplest defences against all of it.

What it is

A rate limit caps how many requests someone can make in a period of time. For example:

  • 5 login attempts per minute per email address.
  • 3 sign-ups per hour per IP address.
  • 100 API requests per minute per user.
  • 50 AI messages per day on the free plan.

When someone exceeds the limit, the server refuses — politely — until the period resets.

What it protects against

  • Password guessing. Without a limit, a bot can try thousands of passwords per minute against one account. With 5 per minute, the attack becomes hopeless. (Password Hashing Explained.)
  • Spam and fake accounts. Limits on sign-ups, contact forms, and comments.
  • Cost attacks. If each request costs you money — AI calls, emails, SMS — a limit caps your worst day. (How to Stop Bots From Running Up Your AI App's Bill.)
  • Scraping. Limits slow down bulk copying of your data.
  • Accidental overload. A bug in someone's script — or your own frontend stuck in a loop — can't take the whole app down.

What a rate-limited response looks like

The standard response is HTTP status 429 Too Many Requests, often with a header saying when to try again:

HTTP/1.1 429 Too Many Requests
Retry-After: 30

Your app should show a friendly message — "Too many attempts. Try again in 30 seconds." — not a generic error. (HTTP Status Codes Explained.)

Who to limit: the "key"

A rate limit counts requests per something:

  • Per IP address — works for logged-out visitors. Imperfect: many people can share one IP (an office, a mobile network), and attackers can use many.
  • Per user account — fairer and more precise for logged-in features.
  • Per email or username being targeted — for login forms, so an attacker can't spread guesses across many IPs against one account.
  • Per API key — for APIs used by other developers.

Good setups combine them: per IP and per account on login, for example.

Where to add rate limits

  1. Login, sign-up, and password reset. Always.
  2. Anything that costs money per request — AI calls, SMS, emails.
  3. Anything that writes data — forms, comments, uploads.
  4. Expensive operations — search, exports, reports.
  5. Your whole API, with a generous general limit as a safety net.

How to add them

The limit must be enforced on the server. A limit in front-end JavaScript is trivially bypassed — attackers don't use your front end.

Options, from least to most work:

  • Your platform or login service may have it built in. Authentication services typically rate-limit login and sign-up for you. Check the settings.
  • A CDN or firewall in front of your app can limit by IP. (What Is a CDN?.)
  • Middleware in your app. Most frameworks have a rate-limiting library — for Express, for example, express-rate-limit:
import rateLimit from "express-rate-limit"

app.use("/api/login", rateLimit({
  windowMs: 60 * 1000,  // 1 minute
  limit: 5,             // 5 attempts per window
}))

One important detail: by default, many libraries keep counts in the server's memory. That resets whenever the app restarts, and doesn't work if you run more than one copy. For real protection, store counts in a shared place like Redis or your database. (Redis: When You Actually Need It and Implementing Rate Limiting.)

Choosing numbers

Start generous for normal use and strict for sensitive actions. A real person rarely submits a login form more than a few times a minute, or signs up more than once an hour. Watch your logs after launch and adjust. Limits so tight that real users hit them are a bug too.

When you hit a rate limit

Your app calls other services — AI providers, GitHub, Stripe — which have their own limits. When you get a 429:

  • Wait and retry, respecting any Retry-After header, with increasing delays between tries.
  • Don't retry instantly in a loop — that makes it worse.
  • Reduce calls: cache results, batch requests, or move work to a background job. (What Is Caching?.)

EasySpawn runs your backend as a real server, so rate limits live on the server where they belong — with Redis available to share counts across restarts. See pricing or join the waitlist.

Related: A Security Checklist for Vibe-Coded Apps · Add Login to an AI-Built App · What Is an API? · What Is a DDoS Attack?

Keep reading