Quick Answer
Rate limiting controls how many requests a client can make to your API within a time window. Without it, any user - or bot - can hammer your endpoints thousands of times per second. AI tools like Bolt, Lovable, and Cursor often leave rate limiting out of generated code unless you ask for it, leaving authentication routes, contact forms, and data endpoints completely unprotected.
Why AI-Generated APIs Ship Without Rate Limiting
Ask any AI coding tool to build a Next.js API route or an Express server, and it will produce functional code quickly. What it often leaves out is rate limiting. The reason is pragmatic: AI tools optimize for getting something that runs, not something that survives production traffic.
Rate limiting is infrastructure - it sits outside the happy path that AI is trained to produce. When generating a login endpoint, the model thinks about accepting credentials, checking the database, returning a token. It doesn't model the threat of an attacker running 50,000 password attempts against that same endpoint.
The OWASP API Security Top 10 (2023) lists Unrestricted Resource Consumption as API4 and names rate limiting as a defense. Akamai's April 2023 State of the Internet report, Slipping through the Security Gaps, found that web application and API attacks grew by 137% in 2022. Cloudflare's DDoS threat report for 2024 Q4 says it mitigated 6.9 million DDoS attacks in that quarter alone, and 21.3 million across 2024.
What Can Go Wrong Without Rate Limiting
The consequences of missing rate limits vary by endpoint type. Login and registration routes allow unlimited brute force attempts. Password reset routes can be abused to flood a user's inbox or enumerate valid email addresses. Contact forms and email-sending endpoints let attackers use your server as a spam relay. AI-powered routes with per-call costs (OpenAI, Anthropic) can drain your budget in minutes.
For Supabase-backed apps built with Lovable or Bolt, the database itself becomes the target. Without rate limiting on your server layer, an attacker can send thousands of queries per second through your API, loading your database and eating into your egress quota, which Supabase's Pro plan bills at $0.09 per GB past the included 250 GB.
| Endpoint Type | Risk Without Rate Limiting | Suggested Limit |
|---|---|---|
| Login / Auth | Brute force, credential stuffing | 5 requests / 15 min per IP |
| Password Reset | Email flood, account enumeration | 3 requests / hour per IP |
| Registration | Fake account creation, spam | 10 requests / hour per IP |
| AI / LLM Proxy | Cost exhaustion | 20 requests / min per user |
| Data / Search | Scraping, DoS | 100 requests / min per IP |
| Contact / Email Send | Spam relay, abuse | 5 requests / hour per IP |
What AI Code Typically Generates
Here is a typical login route from a Bolt or Cursor-generated Next.js app. It works perfectly - and is completely open to abuse:
// ❌ BAD - No rate limiting on auth endpoint
// app/api/auth/login/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { supabase } from '@/lib/supabase';
export async function POST(req: NextRequest) {
const { email, password } = await req.json();
const { data, error } = await supabase.auth.signInWithPassword({
email,
password,
});
if (error) {
return NextResponse.json({ error: error.message }, { status: 401 });
}
return NextResponse.json({ session: data.session });
}
// No rate limiting. No IP tracking. No lockout.
// An attacker can try millions of passwords with no resistance.
How to Add Rate Limiting in Minutes
For Next.js apps, the @upstash/ratelimit library paired with Upstash Redis is a common production choice. It's free at low volumes and integrates with Vercel Edge Functions. For Express apps, express-rate-limit is the standard.
// ✅ GOOD - Rate limiting with Upstash Ratelimit
// app/api/auth/login/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';
import { supabase } from '@/lib/supabase';
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(5, '15 m'), // 5 attempts per 15 minutes
analytics: true,
});
export async function POST(req: NextRequest) {
// Use IP address as rate limit identifier
const ip = req.headers.get('x-forwarded-for') ?? '127.0.0.1';
const { success, remaining, reset } = await ratelimit.limit(ip);
if (!success) {
return NextResponse.json(
{ error: 'Too many login attempts. Try again later.' },
{
status: 429,
headers: {
'Retry-After': String(Math.ceil((reset - Date.now()) / 1000)),
'X-RateLimit-Remaining': String(remaining),
},
}
);
}
const { email, password } = await req.json();
const { data, error } = await supabase.auth.signInWithPassword({
email,
password,
});
if (error) {
return NextResponse.json({ error: error.message }, { status: 401 });
}
return NextResponse.json({ session: data.session });
}
For Express apps, the setup is even simpler:
// ✅ GOOD - express-rate-limit for Node/Express apps
import rateLimit from 'express-rate-limit';
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // 5 requests per window
standardHeaders: true, // Returns rate limit info in headers
legacyHeaders: false,
message: { error: 'Too many login attempts. Try again later.' },
});
app.post('/api/auth/login', loginLimiter, handleLogin);
How to Find Unprotected Endpoints in Your Codebase
Manually auditing every API route is tedious, especially in a Bolt or Lovable project that may have generated dozens of endpoints you didn't explicitly write. The pattern to look for is any export async function POST or app.post() that doesn't call a rate limiting function before processing the request.
VibeDoctor's Vibe Check flags an Express, Fastify, Koa, NestJS or hapi backend that has no rate limiting library or code at all, routes that create Stripe checkout or billing-portal sessions with no rate limiting, and Python login, signup and password-reset POST routes without a rate limit decorator (those with the file and line). It does not check each Next.js route handler, so review those with the search above. Free to sign up.
Beyond automated scanning, manually prioritize these endpoint types first: anything that authenticates a user, sends an email, creates a record, or calls a third-party API with per-request billing.
Rate Limiting at the Infrastructure Layer
Code-level rate limiting is important, but it's not the only layer. If you're deploying on Vercel, you can add rate limiting rules at the edge using Vercel's Firewall (one rule per project on Hobby, 40 on Pro). Cloudflare's free plan provides basic DDoS protection and one rate limiting rule without touching your code.
For Supabase apps, Supabase itself has built-in rate limits on its auth endpoints. But those limits apply to Supabase's APIs - your custom Next.js API routes that proxy data through Supabase still need their own limits. Don't assume the database layer protects your application layer.
FAQ
Does Supabase auth have built-in rate limiting?
Yes, Supabase applies rate limits to its own auth API endpoints. However, if you're building custom API routes in Next.js that call Supabase on the backend, those routes are not rate-limited by Supabase - you need to add your own middleware.
Do I need Redis for rate limiting?
Not necessarily. express-rate-limit uses in-memory storage by default, which works for single-server deployments. For serverless functions (Vercel, Netlify Edge) or multi-server setups, you need a shared store like Redis - Upstash Redis is free at low volumes and integrates easily.
What HTTP status code should rate limited responses return?
429 Too Many Requests is the correct status code. Include a Retry-After header with the number of seconds until the window resets so clients can handle it gracefully.
Should I rate limit by IP or by user?
Both, where possible. IP-based limiting blocks anonymous attackers before they authenticate. User-based limiting prevents authenticated users from abusing your API. For login endpoints specifically, always rate limit by IP since the user isn't authenticated yet.
Can Vercel or Cloudflare replace code-level rate limiting?
Infrastructure-level limits (Cloudflare, Vercel Firewall) are a valuable additional layer but should not replace application-level rate limiting. Infrastructure limits are coarser and harder to tune per-endpoint. Defense in depth means implementing both.