Bayangkan Kamu sudah membangun API Laravel yang berjalan lancar selama berbulan-bulan. Tiba-tiba, response time melonjak drastis. Setelah dicek, ternyata satu client mengirim ribuan request per menit — bisa jadi bug di kode mereka, script scraping, atau serangan yang disengaja. Tanpa rate limiting, server harus melayani semua request tersebut sampai resource habis.
Rate limiting adalah teknik untuk membatasi jumlah request yang diterima server dari satu sumber (user, IP address, atau API key) dalam periode waktu tertentu. Teknik ini juga sering disebut request throttling. Artikel ini membahas konsep rate limiting secara menyeluruh, algoritma di baliknya, dan cara implementasi praktis di Laravel — dari versi 8 hingga 11.
Apa Itu Rate Limiting?
Rate limiting adalah mekanisme kontrol yang membatasi frekuensi request yang boleh dilakukan oleh satu entitas dalam jendela waktu tertentu. Entitas di sini bisa berupa user yang sudah login, IP address, atau API key — tergantung strategi yang kamu pilih.
Konsepnya mirip pintu tol: hanya sejumlah kendaraan yang bisa lewat per menit. Kendaraan yang datang melebihi kapasitas harus menunggu atau dialihkan. Bedanya, di dunia web, request yang melebihi limit biasanya langsung ditolak dengan response error.
Mengapa Rate Limiting Penting bagi Kamu?
Ada beberapa alasan kenapa rate limiting bukan sekadar fitur tambahan, tapi kebutuhan dasar untuk aplikasi yang kamu bangun:
Perlindungan dari serangan DoS/DDoS sederhana. Tanpa batasan, satu sumber bisa membanjiri server dengan request hingga server tidak bisa melayani user lain. Rate limiting tidak menghentikan serangan DDoS skala besar (itu butuh solusi di level infrastruktur seperti Cloudflare), tapi setidaknya mencegah serangan sederhana dari satu sumber.
Pencegahan brute force. Endpoint login tanpa throttle memungkinkan attacker mencoba ribuan kombinasi password per menit. Dengan rate limiting, jumlah percobaan bisa dibatasi — misalnya maksimal 5 kali per menit.
Mencegah penyalahgunaan API. Satu client yang rakus bisa menghabiskan seluruh kapasitas API, sehingga user lain tidak mendapat akses. Rate limiting memastikan semua user mendapat porsi yang adil (fair usage).
Manajemen resource server. CPU, memory, dan database connection punya batas. Rate limiting membantu menjaga resource tetap stabil di bawah beban tinggi.
Cost control di cloud. Pada layanan cloud yang billing-nya berbasis usage, request berlebihan bisa membuat tagihan membengkak tanpa kamu sadari.
Algoritma yang Umum Digunakan
Di balik rate limiting ada beberapa algoritma dengan karakteristik berbeda. Memahami perbedaannya berguna untuk memilih pendekatan yang tepat.
Fixed Window Counter
Algoritma ini membagi waktu ke dalam jendela tetap. Misalnya dengan limit 100 request per menit, window-nya: 00:00–00:59, 01:00–01:59, dan seterusnya. Setiap request menambah counter. Counter reset ke 0 saat window baru dimulai.
Kelebihannya: sederhana dan hemat resource karena hanya perlu menyimpan satu counter per window.
Kelemahannya: rentan burst di batas window. User bisa mengirim 100 request di detik ke-59, lalu 100 lagi di detik ke-60 (window baru), sehingga total 200 request dalam 2 detik. Secara teknis tidak melanggar aturan, tapi efeknya sama dengan tidak ada limit.
Laravel menggunakan fixed window counter sebagai algoritma default pada ThrottleRequests middleware.
Sliding Window
Berbeda dengan fixed window, sliding window menghitung request dalam N detik terakhir dari waktu saat ini, bukan dari awal window tetap. Kalau limit-nya 100 per menit, maka setiap saat sistem menghitung berapa request yang masuk dalam 60 detik terakhir.
Hasilnya lebih akurat dan tidak rentan burst di batas window. Trade-off-nya: butuh penyimpanan lebih besar karena harus menyimpan timestamp setiap request (sliding window log), atau melakukan perhitungan proporsional dari dua window bersebelahan (sliding window counter).
Token Bucket
Bayangkan sebuah bucket yang berisi token. Token terisi ulang secara berkala — misalnya 10 token per detik. Setiap request mengambil 1 token dari bucket. Kalau bucket kosong, request ditolak.
Keunggulan token bucket: mengizinkan burst singkat. Kalau bucket berkapasitas 50 token dan terisi penuh, user bisa mengirim 50 request sekaligus. Setelah itu, mereka harus menunggu token terisi lagi. Ini cocok untuk traffic yang memang bursty secara natural.
Leaky Bucket
Leaky bucket bekerja seperti antrian. Request masuk ke bucket dan diproses pada rate yang konstan — misalnya 10 request per detik, tidak peduli berapa banyak yang masuk. Kalau bucket penuh, request baru ditolak.
Hasilnya: output rate yang sangat smooth dan konsisten. Tapi ada trade-off: request mungkin harus menunggu di antrian, sehingga menambah latency.
| Algoritma | Cara Kerja | Kelebihan | Kelemahan |
|---|---|---|---|
| Fixed Window | Counter per window tetap | Sederhana, hemat resource | Rentan burst di batas window |
| Sliding Window | Hitung request dalam N detik terakhir | Akurat, tidak ada burst | Butuh penyimpanan lebih |
| Token Bucket | Token terisi berkala, request ambil token | Izinkan burst singkat | Implementasi lebih kompleks |
| Leaky Bucket | Antrian diproses pada rate konstan | Output smooth | Bisa menambah latency |
HTTP Response: Status Code 429 dan Rate Limit Headers
Saat client melampaui batas rate limit, server mengembalikan HTTP status code 429 Too Many Requests. Status code ini didefinisikan dalam RFC 6585. Laravel otomatis mengembalikan response 429 melalui ThrottleRequests middleware tanpa konfigurasi tambahan.
Rate Limit Headers
Laravel juga otomatis menyertakan headers yang memberi tahu client tentang status rate limit mereka. Headers ini bukan bagian dari standar HTTP resmi, tapi sudah menjadi konvensi industri yang sangat luas digunakan.
Contoh response headers saat request normal (masih dalam limit):
HTTP/1.1 200 OK
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 42
X-RateLimit-Limit menunjukkan total request yang diperbolehkan per window. X-RateLimit-Remaining menunjukkan sisa kuota.
Saat limit sudah tercapai:
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
Retry-After: 30
Header Retry-After memberi tahu client berapa detik yang harus ditunggu sebelum mengirim request lagi. Header ini didefinisikan dalam RFC 7231.
Informasi ini penting bagi consumer API. Tanpa headers ini, client tidak tahu kapan boleh request lagi dan biasanya akan terus retry, justru memperburuk situasi.
Rate Limiting di Laravel: Implementasi Lengkap
Laravel menyediakan dua pendekatan utama untuk rate limiting: ThrottleRequests middleware untuk HTTP request, dan RateLimiter facade untuk rate limiting di mana saja dalam kode.
ThrottleRequests Middleware
Cara paling cepat menerapkan rate limiting di Laravel adalah menggunakan throttle middleware langsung di route. Format parameter: throttle:maxAttempts,decayMinutes.
Route::middleware(['throttle:60,1'])->group(function () {
Route::get('/posts', [PostController::class, 'index']);
Route::get('/posts/{post}', [PostController::class, 'show']);
});
Kode di atas membatasi 60 request per 1 menit untuk semua route di dalam group tersebut. Class yang menangani ini adalah Illuminate\Routing\Middleware\ThrottleRequests.
Pendekatan inline seperti ini cukup untuk kasus sederhana. Tapi kalau butuh logic yang lebih fleksibel — misalnya limit berbeda untuk user premium, atau segmentasi berdasarkan email — gunakan named rate limiter.
Named Rate Limiter dengan RateLimiter::for()
Named rate limiter memungkinkan definisi aturan rate limiting yang lebih kompleks dan reusable. Cara mendefinisikannya berbeda tergantung versi Laravel.
Laravel 8–10 — definisikan di RouteServiceProvider::configureRateLimiting():
// App\Providers\RouteServiceProvider
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Support\Facades\RateLimiter;
protected function configureRateLimiting()
{
RateLimiter::for('api', function (Request $request) {
return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
});
}
Laravel 11+ — RouteServiceProvider sudah dihapus, definisikan di AppServiceProvider::boot():
// App\Providers\AppServiceProvider
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
public function boot(): void
{
RateLimiter::for('api', function (Request $request) {
return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
});
}
Setelah didefinisikan, attach ke route menggunakan nama limiter:
Route::middleware(['throttle:api'])->group(function () {
Route::get('/user', function (Request $request) {
return $request->user();
});
});
Segmentasi Rate Limit
Method ->by() pada class Limit menentukan bagaimana rate limit disegmentasi. Ini penting karena menentukan siapa yang "berbagi" kuota.
Contoh: user yang sudah login mendapat limit lebih tinggi, sementara guest dibatasi berdasarkan IP:
RateLimiter::for('api', function (Request $request) {
return $request->user()
? Limit::perMinute(100)->by($request->user()->id)
: Limit::perMinute(10)->by($request->ip());
});
Perlu diperhatikan: segmentasi berdasarkan IP saja bisa bermasalah kalau banyak user di belakang NAT atau proxy yang sama — mereka berbagi satu IP address dan akan berbagi kuota rate limit juga. Segmentasi per-user (authenticated) lebih akurat dalam kasus ini.
Multiple Rate Limits
Satu rate limiter bisa menerapkan beberapa aturan sekaligus dengan mengembalikan array Limit. Ini berguna untuk menerapkan batasan berlapis.
Contoh pada endpoint upload — batasan global 1000 request per menit untuk seluruh endpoint, plus batasan 10 upload per menit per user:
RateLimiter::for('uploads', function (Request $request) {
return [
Limit::perMinute(1000),
Limit::perMinute(10)->by($request->user()->id),
];
});
Dengan konfigurasi ini, meskipun global limit masih tersisa, satu user yang sudah upload 10 kali dalam semenit tetap diblokir. Dua layer perlindungan sekaligus.
Custom Response untuk 429
Response default Laravel saat limit tercapai sudah cukup untuk kebanyakan kasus. Tapi kalau butuh response kustom — misalnya response JSON dengan format tertentu untuk API:
RateLimiter::for('api', function (Request $request) {
return Limit::perMinute(60)
->by($request->user()?->id ?: $request->ip())
->response(function (Request $request, array $headers) {
return response()->json([
'message' => 'Terlalu banyak request. Coba lagi nanti.',
], 429, $headers);
});
});
Parameter $headers yang diterima callback sudah berisi X-RateLimit-* dan Retry-After headers dari Laravel. Pastikan untuk meneruskannya ke response agar client tetap mendapat informasi rate limit.
RateLimiter Facade: Rate Limiting di Luar HTTP Request
Rate limiting tidak selalu tentang HTTP request. Ada skenario di mana kamu perlu membatasi aksi lain — pengiriman email, notifikasi push, atau pemrosesan job di queue. Laravel menyediakan RateLimiter facade yang bisa digunakan di mana saja.
Method attempt()
RateLimiter::attempt() adalah method utama untuk rate limiting programmatic. Method ini menjalankan callback hanya jika limit belum tercapai:
use Illuminate\Support\Facades\RateLimiter;
$executed = RateLimiter::attempt(
'send-message:'.$user->id,
$perMinute = 5,
function () use ($user, $message) {
// Kirim pesan
$user->sendMessage($message);
}
);
if (! $executed) {
return 'Terlalu banyak pesan. Tunggu sebentar.';
}
Parameter pertama adalah key unik — biasanya kombinasi aksi dan identifier user. Parameter kedua adalah jumlah attempt yang diizinkan per menit.
Method Pendukung
RateLimiter juga menyediakan beberapa method untuk mengecek status tanpa melakukan aksi:
// Cek apakah limit sudah tercapai
if (RateLimiter::tooManyAttempts('send-message:'.$user->id, $perMinute = 5)) {
$seconds = RateLimiter::availableIn('send-message:'.$user->id);
return "Coba lagi dalam {$seconds} detik.";
}
// Cek sisa attempt
$remaining = RateLimiter::remaining('send-message:'.$user->id, 5);
// Reset counter (misalnya setelah user berhasil login)
RateLimiter::clear('login-attempts:'.$user->id);
Method clear() berguna untuk skenario seperti reset counter login setelah user berhasil login, sehingga percobaan gagal sebelumnya tidak menghantui mereka.
Redis sebagai Cache Driver untuk Rate Limiting
Secara default, rate limiter Laravel menggunakan cache driver yang dikonfigurasi di config/cache.php. Untuk development, driver file atau database bisa berjalan. Tapi untuk production dengan traffic tinggi, Redis sangat direkomendasikan.
Kenapa Redis? Rate limiting butuh operasi atomic — increment counter dan cek limit harus terjadi sebagai satu operasi yang tidak bisa diinterupsi. Redis mendukung ini secara native. Laravel bahkan menyediakan class khusus ThrottleRequestsWithRedis yang menggunakan Lua scripting untuk memastikan atomicity.
Konfigurasi Redis Throttle
Laravel 11+ — tambahkan konfigurasi di bootstrap/app.php:
->withMiddleware(function (Middleware $middleware) {
$middleware->throttleWithRedis();
})
Laravel 8–10 — map throttle middleware ke class Redis di HTTP Kernel. Property-nya berbeda tergantung versi: $routeMiddleware di Laravel 8, $middlewareAliases di Laravel 9-10.
// App\Http\Kernel.php
// Laravel 8
protected $routeMiddleware = [
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequestsWithRedis::class,
// ...
];
// Laravel 9-10
protected $middlewareAliases = [
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequestsWithRedis::class,
// ...
];
Untuk memisahkan cache rate limiter dari cache aplikasi utama, tambahkan key limiter di config/cache.php yang menunjuk ke connection Redis khusus. Ini mencegah operasi rate limiting berkompetisi dengan cache data aplikasi.
Skenario Praktis
Login Throttling (Anti Brute Force)
Login endpoint adalah target utama brute force. Berikut contoh rate limiter khusus login dengan multiple limits:
RateLimiter::for('login', function (Request $request) {
return [
Limit::perMinute(500),
Limit::perMinute(5)->by($request->input('email')),
];
});
Global limit 500 per menit mencegah serangan massal, sementara per-email limit 5 per menit mencegah brute force pada akun tertentu. Laravel sendiri sudah menyediakan login throttling built-in melalui trait ThrottleLogins dengan default 5 percobaan dan lockout 60 detik.
API dengan Tier Berbeda
Public API biasanya membedakan limit berdasarkan subscription tier:
RateLimiter::for('api', function (Request $request) {
$user = $request->user();
if (! $user) {
return Limit::perMinute(10)->by($request->ip());
}
return match ($user->subscription_tier) {
'premium' => Limit::perHour(1000)->by($user->id),
'free' => Limit::perHour(100)->by($user->id),
default => Limit::perHour(100)->by($user->id),
};
});
Untuk internal service atau trusted partner, Limit::none() bisa digunakan untuk melewatkan rate limiting:
if ($user->isTrustedService()) {
return Limit::none();
}
Form Submission (Anti Spam)
Contact form atau comment form yang tidak di-throttle rentan spam. Batasi submission per IP:
RateLimiter::for('contact-form', function (Request $request) {
return Limit::perMinute(3)->by($request->ip());
});
Kesalahan Umum dan Tips
Tidak menerapkan rate limiting sama sekali. API yang terbuka tanpa proteksi tinggal menunggu waktu untuk diserang atau disalahgunakan.
Rate limit terlalu longgar atau terlalu ketat. Limit 10.000 request per menit hampir sama dengan tidak ada limit. Sebaliknya, 5 request per menit untuk API data bisa mengganggu user normal. Tentukan angka berdasarkan penggunaan aktual — monitor traffic terlebih dahulu sebelum menetapkan limit.
Hanya throttle berdasarkan IP. Di belakang shared NAT atau corporate proxy, ratusan user bisa berbagi satu IP. Mereka akan saling "makan" kuota rate limit. Kombinasikan segmentasi IP dengan user ID untuk hasil yang lebih akurat.
Tidak menangani 429 di sisi client. Client yang menerima 429 tapi terus retry tanpa jeda akan memperburuk situasi. Implementasikan exponential backoff: tunggu 1 detik, lalu 2 detik, lalu 4 detik, dan seterusnya.
Menggunakan file atau database cache driver di production. File I/O dan database query jauh lebih lambat dibanding Redis untuk operasi increment/decrement yang konstan. Gunakan Redis untuk rate limiting di production.
Tidak mengirimkan rate limit headers. API yang tidak mengirim X-RateLimit-Limit dan X-RateLimit-Remaining menyulitkan consumer. Mereka tidak tahu berapa sisa kuota dan kapan harus mengurangi request. Laravel mengirimkan headers ini secara otomatis melalui ThrottleRequests middleware — pastikan middleware ini terpasang.
Penutup
Rate limiting adalah mekanisme dasar untuk menjaga keamanan dan stabilitas aplikasi web. Tanpa rate limiting, API dan endpoint sensitif seperti login terbuka terhadap brute force, abuse, dan lonjakan traffic yang bisa menjatuhkan server.
Laravel menyediakan tooling yang lengkap dan mudah digunakan: ThrottleRequests middleware untuk rate limiting HTTP request, RateLimiter::for() untuk mendefinisikan aturan yang fleksibel, dan RateLimiter::attempt() untuk rate limiting di luar HTTP context.
Untuk production, gunakan Redis sebagai cache driver, terapkan rate limit bertingkat (global + per-user + per-endpoint), dan pastikan API mengirimkan rate limit headers agar consumer tahu batasannya. Mulai dari endpoint paling kritis — login dan API publik — lalu perluas sesuai kebutuhan.
Untuk membangun sistem keamanan yang komprehensif, gabungkan rate limiting dengan autentikasi yang kuat melalui autentikasi Laravel dan perlindungan otorisasi.
Referensi: RFC 6585 — HTTP 429 Too Many Requests, RFC 7231 — Header Retry-After, Dokumentasi Laravel 11 Rate Limiting.
Fitur komentar belum diaktifkan oleh administrator.