Apply rate limiting to all public-facing API endpoints. Without rate limits, a single attacker can overwhelm your server, exhaust your database connections, or brute-force authentication — taking down the service for all users.
Without rate limiting, your API has no defense against volume-based attacks. A single script can send thousands of requests per second, exhausting database connections, consuming server CPU, and blocking legitimate users. Brute-force attacks against login endpoints can try millions of passwords. Scraping bots can download your entire dataset. Rate limiting is the minimum viable protection against these attacks.
BeforeMerge scans your pull requests against this rule and 4+ others. Get actionable feedback before code ships.
Without rate limiting, your API endpoints accept unlimited requests from any source. This creates multiple attack vectors:
Rate limiting doesn't prevent all abuse, but it puts a ceiling on the damage any single actor can cause. It is a fundamental security control that every public API needs.
Apply rate limiting to every public-facing endpoint. Use different limits for different endpoint types: authentication endpoints should have strict limits (5-10 requests per minute), data APIs should have moderate limits (100-1000 per minute), and read-heavy endpoints can be more generous.
// BAD: no rate limiting — unlimited login attempts
export async function POST(request: Request) {
const { email, password } = await request.json();
const user = await db.user.findUnique({ where: { email } });
if (!user || !await bcrypt.compare(password, user.passwordHash)) {
return Response.json({ error: "Invalid credentials" }, { status: 401 });
}
const token = await createSession(user.id);
return Response.json({ token });
}import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis";
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(5, "1 m"), // 5 requests per minute
analytics: true,
});
export async function POST(request: Request) {
// Rate limit by IP address
const ip = request.headers.get("x-forwarded-for") ?? "127.0.0.1";
const { success, limit, remaining, reset } = await ratelimit.limit(ip);
if (!success) {
return Response.json(
{ error: "Too many requests. Try again later." },
{
status: 429,
headers: {
"X-RateLimit-Limit": limit.toString(),
"X-RateLimit-Remaining": remaining.toString(),
"X-RateLimit-Reset": reset.toString(),
},
}
);
}
const { email, password } = await request.json();
const user = await db.user.findUnique({ where: { email } });
if (!user || !await bcrypt.compare(password, user.passwordHash)) {
return Response.json({ error: "Invalid credentials" }, { status: 401 });
}
const token = await createSession(user.id);
return Response.json({ token });
}Check if your API routes have rate limiting middleware:
grep -rn "ratelimit\|rate.limit\|rateLimiter" --include="*.ts" app/api/If no results, your API likely has no rate limiting.
Retry-After and X-RateLimit-* headers