API Rate Limiting: Stop Getting Abused
Your API is getting hammered. Here's how to implement rate limiting that actually works without blocking real users.
Table of Contents
- Why Rate Limiting Matters
- Basic Rate Limiting
- Better: Per-User Rate Limiting
- Advanced: Different Limits for Different Endpoints
- Node.js Rate Limiting
- Redis-Based Rate Limiting
- Strategies That Work
- 1. Tiered Limits
- 2. Burst Allowance
- 3. IP + User Combined
- Custom Rate Limiting Logic
- Handling Rate Limit Responses
- Return Useful Headers
- Custom Error Response
- Real Project Example
- Monitoring Rate Limits
- When to Adjust Limits
- Bottom Line
API Rate Limiting: Stop Getting Abused
Your API is getting hammered by bots. Or one client is making 10,000 requests per minute.
Here's how to fix it.
Why Rate Limiting Matters
Real story: Client's API went down. One user's script had a bug, made 50,000 requests in 10 minutes. Crashed the server. Affected all users.
Rate limiting would've stopped it at request 100.
Basic Rate Limiting (Laravel)
Route::middleware('throttle:60,1')->group(function () {
Route::get('/api/users', [UserController::class, 'index']);
});
60 requests per minute. Simple.
Problem: All users share the same limit. One bad actor affects everyone.
Better: Per-User Rate Limiting
Route::middleware('throttle:100,1,user')->group(function () {
Route::get('/api/users', [UserController::class, 'index']);
});
Each authenticated user gets 100 requests/minute.
Much better. Bad actors only affect themselves.
Advanced: Different Limits for Different Endpoints
// Read endpoints: Higher limit
Route::middleware('throttle:200,1')->group(function () {
Route::get('/api/products', [ProductController::class, 'index']);
Route::get('/api/categories', [CategoryController::class, 'index']);
});
// Write endpoints: Lower limit
Route::middleware('throttle:30,1')->group(function () {
Route::post('/api/orders', [OrderController::class, 'store']);
Route::post('/api/payments', [PaymentController::class, 'process']);
});
Reading is cheap. Writing is expensive. Limit accordingly.
Node.js Rate Limiting
Using Express:
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 60 * 1000, // 1 minute
max: 100, // 100 requests per minute
message: 'Too many requests, try again later',
standardHeaders: true,
legacyHeaders: false,
});
app.use('/api/', limiter);
Redis-Based Rate Limiting
For multiple servers, use Redis:
// Laravel with Redis
Route::middleware('throttle:100,1,redis')->group(function () {
Route::get('/api/data', [DataController::class, 'index']);
});
// Node.js with Redis
const RedisStore = require('rate-limit-redis');
const redis = require('redis');
const client = redis.createClient();
const limiter = rateLimit({
store: new RedisStore({
client: client,
}),
windowMs: 60 * 1000,
max: 100,
});
Why Redis: Multiple servers share the same rate limit counter. User can't bypass by hitting different servers.
Strategies That Work
1. Tiered Limits
// Free tier
Route::middleware('throttle:50,1')->group(function () {
// Limited endpoints
});
// Paid tier
Route::middleware('throttle:500,1')->group(function () {
// Higher limits
});
// Enterprise tier
Route::middleware('throttle:5000,1')->group(function () {
// Highest limits
});
2. Burst Allowance
Allow short bursts, but limit sustained traffic:
// Allow 10 requests immediately, then 1 per second
Route::middleware('throttle:10,1,per_second')->group(function () {
Route::post('/api/search', [SearchController::class, 'search']);
});
3. IP + User Combined
// Limit by IP for unauthenticated
Route::middleware('throttle:20,1')->group(function () {
Route::post('/api/register', [AuthController::class, 'register']);
});
// Limit by user for authenticated
Route::middleware(['auth', 'throttle:100,1,user'])->group(function () {
Route::get('/api/dashboard', [DashboardController::class, 'index']);
});
Custom Rate Limiting Logic
Sometimes you need custom rules:
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Support\Facades\RateLimiter;
RateLimiter::for('api', function (Request $request) {
// Premium users get higher limits
if ($request->user()?->isPremium()) {
return Limit::perMinute(1000);
}
// Regular users
if ($request->user()) {
return Limit::perMinute(100);
}
// Unauthenticated: Very limited
return Limit::perMinute(10);
});
Use it:
Route::middleware('throttle:api')->group(function () {
// Your routes
});
Handling Rate Limit Responses
Return Useful Headers
// Laravel does this automatically
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1678901234
Retry-After: 60
Clients know when they can retry.
Custom Error Response
// app/Exceptions/Handler.php
use Illuminate\Http\Exceptions\ThrottleRequestsException;
public function render($request, Throwable $exception)
{
if ($exception instanceof ThrottleRequestsException) {
return response()->json([
'error' => 'Rate limit exceeded',
'retry_after' => $exception->getHeaders()['Retry-After'] ?? 60,
], 429);
}
return parent::render($request, $exception);
}
Real Project Example
Client: Payment processing API
Problem: Getting 50,000+ requests/hour, server struggling
Solution: Implemented tiered rate limiting
// Public endpoints: Very limited
Route::middleware('throttle:10,1')->group(function () {
Route::post('/api/webhook', [WebhookController::class, 'handle']);
});
// Authenticated: Normal limits
Route::middleware(['auth', 'throttle:100,1'])->group(function () {
Route::post('/api/payments', [PaymentController::class, 'process']);
});
// Verified partners: High limits
Route::middleware(['auth', 'verified', 'throttle:1000,1'])->group(function () {
Route::post('/api/bulk-payments', [PaymentController::class, 'bulk']);
});
Results:
- Server load: 85% → 45%
- Legitimate requests: No impact
- Bot traffic: Blocked 95%
- Costs: Reduced by 40%
Monitoring Rate Limits
Track who's hitting limits:
RateLimiter::for('api', function (Request $request) {
$limit = Limit::perMinute(100);
// Log when users hit limits
if (RateLimiter::tooManyAttempts($request->user()->id, 100)) {
Log::warning('User hit rate limit', [
'user_id' => $request->user()->id,
'ip' => $request->ip(),
]);
}
return $limit;
});
Helps identify:
- Buggy client code
- Potential abuse
- Need for higher limits
When to Adjust Limits
Increase limits if:
- Legitimate users hitting limits
- Complaints about "too many requests"
- Business needs higher throughput
Decrease limits if:
- Server struggling
- High bot traffic
- Abuse detected
Bottom Line
Rate limiting is essential for any public API.
Start with simple limits. Adjust based on real usage. Monitor and iterate.
Better to start strict and loosen up than start loose and get abused.
Building APIs?
We build secure, scalable APIs for Nigerian businesses.
📞 WhatsApp: +234 708 711 0468
📧 info@raspibtech.com
📍 Lagos Island
Related:
Need Help with Your Project?
Let's discuss how Raspib Technology can help transform your business
Related Articles
Laravel 11: What Changed and Why You Should Care
Laravel 11 is out. Slimmer structure, better performance, and features that actually save time. Here's what matters.
Read more →Laravel 12: The Upgrade You've Been Waiting For
Laravel 12 brings major improvements. Here's what changed and why it matters for your projects.
Read more →Next.js 15: The Features That Actually Matter
Next.js 15 changed a lot. Here's what affects your projects, what breaks, and when to upgrade.
Read more →