Rate Limiting API: Algoritma, 429 & Contoh di Laravel

Rate limiting adalah pembatasan jumlah request yang boleh dilakukan satu klien dalam rentang waktu tertentu, misalnya 60 request per menit per pengguna. Jika batas terlampaui, API...

Rate Limiting API: Algoritma, 429 & Contoh di Laravel
Iklan

Rate limiting adalah pembatasan jumlah request yang boleh dilakukan satu klien dalam rentang waktu tertentu, misalnya 60 request per menit per pengguna. Jika batas terlampaui, API membalas dengan status 429 Too Many Requests dan header Retry-After yang memberi tahu klien kapan boleh mencoba lagi. Di Laravel, Anda cukup mendefinisikan limiter dengan RateLimiter::for() lalu memasang middleware throttle di route.

Artikel ini menjelaskan kenapa rate limiting penting, tiga algoritma yang paling sering dipakai (fixed window, sliding window, token bucket), header respons yang benar, contoh kode Laravel, pilihan kunci per pengguna vs per IP, serta jebakan saat aplikasi berada di belakang proxy atau Cloudflare. Jika konsep API masih baru bagi Anda, mulai dulu dari apa itu REST API dan cara kerjanya.

Kenapa API perlu rate limiting

Tanpa batas, satu klien bisa menghabiskan sumber daya yang seharusnya dibagi bersama. Beberapa alasan konkret:

Iklan
  • Mencegah brute force. Endpoint login, reset password, dan verifikasi OTP adalah sasaran tebak-tebakan otomatis. Membatasi percobaan per akun dan per IP membuat serangan ini tidak praktis.
  • Melindungi server dan database. Script yang salah tulis, misalnya loop tanpa jeda, bisa membanjiri API dan membuat pengguna lain ikut lambat.
  • Mengendalikan biaya. Jika endpoint Anda memanggil layanan berbayar (SMS, email, API pihak ketiga), setiap request berarti biaya.
  • Keadilan antarpengguna. Batas yang jelas memastikan satu pengguna berat tidak mengorbankan pengguna lain, dan bisa menjadi dasar paket berlangganan (gratis vs premium).
  • Mengurangi scraping. Rate limiting tidak menghentikan scraper yang gigih, tetapi memperlambat dan membuatnya lebih mahal.

Perlu dicatat, rate limiting di level aplikasi bukan perlindungan DDoS. Serangan volumetrik besar harus ditahan sebelum mencapai server, misalnya oleh CDN atau firewall jaringan. Rate limiting aplikasi bertugas mengatur perilaku klien yang "sah tapi berlebihan".

Tiga algoritma yang paling sering dipakai

Fixed window (jendela tetap)

Waktu dibagi menjadi jendela tetap, misalnya per menit. Setiap request menambah penghitung untuk jendela saat ini; jika penghitung melewati batas, request ditolak. Saat jendela baru dimulai, penghitung kembali ke nol.

Kelebihannya sangat sederhana dan hemat memori: cukup satu angka per klien. Kekurangannya ada di batas jendela. Dengan batas 60 per menit, klien bisa mengirim 60 request di detik ke-59 dan 60 lagi di detik ke-61. Hasilnya 120 request dalam dua detik, dua kali lipat dari yang Anda maksudkan.

Sliding window (jendela bergeser)

Sliding window menghitung request dalam rentang waktu yang terus bergeser ke belakang dari "sekarang", sehingga lonjakan di batas jendela tidak terjadi. Ada dua varian:

Iklan
  • Sliding window log: menyimpan timestamp setiap request, lalu menghitung berapa yang masih berada dalam 60 detik terakhir. Akurat, tetapi memorinya sebanding dengan jumlah request.
  • Sliding window counter: menyimpan penghitung jendela sekarang dan jendela sebelumnya, lalu memperkirakan jumlah dengan bobot. Misalnya, jika kita berada 25% di dalam menit ini, perkiraannya adalah hitungan menit ini ditambah 75% hitungan menit lalu. Hemat memori dan cukup akurat untuk sebagian besar kebutuhan.

Token bucket (ember token)

Bayangkan ember berkapasitas misalnya 20 token. Token diisi ulang dengan laju tetap, misalnya 1 token per detik, sampai ember penuh. Setiap request mengambil satu token; jika ember kosong, request ditolak.

Token bucket memungkinkan burst yang terkendali: klien yang lama diam bisa mengirim 20 request sekaligus, tetapi rata-rata jangka panjangnya tetap 1 per detik. Ini cocok untuk perilaku klien nyata, misalnya aplikasi mobile yang memuat beberapa data sekaligus saat dibuka. Varian dekatnya adalah leaky bucket, yang menghaluskan aliran keluar dengan laju konstan; modul limit_req di Nginx bekerja dengan prinsip ini.

AlgoritmaKelebihanKekuranganCocok untuk
Fixed windowPaling sederhana, hemat memoriLonjakan di batas jendelaBatas kasar, login, form
Sliding window logPaling akuratMemori besarBatas kecil yang harus presisi
Sliding window counterAkurat dan hematBerupa perkiraanAPI publik umum
Token bucketMengizinkan burst terkendaliDua parameter perlu disetelAPI yang dipakai aplikasi klien

Respons yang benar: 429 dan Retry-After

Status 429 Too Many Requests didefinisikan di RFC 6585 khusus untuk kasus ini. Jangan memakai 403 atau 500, karena klien tidak akan tahu bahwa masalahnya sementara. Sertakan header Retry-After (RFC 9110) berisi jumlah detik tunggu atau tanggal HTTP.

HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 37
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0

{"message": "Too Many Attempts."}

Header X-RateLimit-Limit dan X-RateLimit-Remaining bukan standar resmi, tetapi sudah menjadi konvensi umum dan dikirim Laravel secara bawaan. IETF juga sedang menyusun header standar RateLimit dan RateLimit-Policy, tetapi statusnya masih draft, jadi konvensi X-RateLimit-* masih yang paling banyak dipakai saat ini.

Iklan

Di sisi klien, hormati Retry-After. Jika tidak ada, gunakan exponential backoff dengan sedikit jitter acak, agar banyak klien tidak mencoba ulang pada detik yang sama.

Rate limiting di Laravel

Cara cepat: throttle dengan angka

// 60 request per 1 menit
Route::middleware('throttle:60,1')->group(function () {
    Route::get('/produk', [ProdukController::class, 'index']);
});

Format throttle:maks,menit. Kunci pembatasnya otomatis: ID pengguna jika sudah login, IP jika belum.

Named limiter dengan RateLimiter::for()

Untuk kontrol lebih, definisikan limiter bernama. Di Laravel 11 ke atas, letakkan di method boot() pada App\Providers\AppServiceProvider. Di Laravel 10, tempatnya di configureRateLimiting() pada RouteServiceProvider.

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());
    });

    RateLimiter::for('login', function (Request $request) {
        return [
            Limit::perMinute(5)->by(strtolower($request->input('email')) . '|' . $request->ip()),
            Limit::perMinute(20)->by($request->ip()),
        ];
    });
}

Limiter login memakai dua batas sekaligus: 5 percobaan per kombinasi email dan IP, plus 20 percobaan per IP untuk mencegah satu IP mencoba banyak email. Pasang di route:

Iklan
Route::post('/login', [AuthController::class, 'login'])->middleware('throttle:login');

Route::middleware(['auth:sanctum', 'throttle:api'])->group(function () {
    Route::get('/pesanan', [PesananController::class, 'index']);
});

Batas berbeda per paket langganan

RateLimiter::for('api', function (Request $request) {
    $user = $request->user();

    if (! $user) {
        return Limit::perMinute(30)->by($request->ip());
    }

    return $user->isPremium()
        ? Limit::perMinute(600)->by($user->id)
        : Limit::perMinute(60)->by($user->id);
});

Selain perMinute, tersedia perHour, perDay, perMinutes($menit, $maks), dan Limit::none() untuk tanpa batas, misalnya bagi admin internal. Laravel 11 juga menambahkan perSecond.

Respons 429 kustom

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);
    });

Teruskan $headers agar Retry-After dan header rate limit tetap terkirim.

Membatasi aksi tertentu dengan RateLimiter::attempt()

Tidak semua pembatasan berbentuk route. Misalnya Anda ingin membatasi pengiriman OTP maksimal 3 kali per 10 menit per nomor:

$key = 'kirim-otp:' . $nomorHp;

$terkirim = RateLimiter::attempt($key, 3, function () use ($nomorHp) {
    OtpService::kirim($nomorHp);
}, 600); // decay 600 detik

if (! $terkirim) {
    $detik = RateLimiter::availableIn($key);
    return back()->withErrors(['otp' => "Coba lagi dalam {$detik} detik."]);
}

Method lain yang berguna: tooManyAttempts(), hit(), remaining(), dan clear() untuk mereset hitungan, misalnya setelah login berhasil.

Iklan
Dapatkan Update Terbaru

Berlangganan gratis. Artikel & tutorial coding terbaru langsung ke email kamu. Tanpa spam.

Penyimpanan: gunakan cache bersama

Rate limiter Laravel menyimpan hitungan di cache default aplikasi. Jika Anda menjalankan beberapa server aplikasi, pastikan semuanya memakai cache yang sama seperti Redis, bukan driver file atau array. Jika tidak, setiap server menghitung sendiri-sendiri dan batas efektifnya berlipat. Anda bisa menentukan store khusus lewat konfigurasi cache.limiter.

Per pengguna atau per IP?

Pemilihan kunci (by()) menentukan siapa yang dibatasi, dan tiap pilihan punya konsekuensi:

  • Per IP: satu-satunya pilihan untuk pengunjung yang belum login. Kelemahannya, banyak pengguna bisa berbagi satu IP publik (jaringan kantor, kampus, operator seluler dengan CGNAT), sehingga mereka saling menghabiskan kuota. Sebaliknya, penyerang dengan banyak IP bisa lolos.
  • Per pengguna: lebih adil dan akurat untuk endpoint yang memerlukan autentikasi. Satu akun tidak bisa menghindari batas dengan berganti IP.
  • Per API key / token: cocok untuk API publik yang dipakai aplikasi pihak ketiga.
  • Kombinasi: untuk login, gabungkan identitas yang dicoba (email) dengan IP, ditambah batas per IP yang lebih longgar.

Di belakang proxy, load balancer, atau Cloudflare

Ini kesalahan yang sering tidak disadari. Jika aplikasi berada di belakang reverse proxy atau Cloudflare, $request->ip() bisa mengembalikan IP proxy, bukan IP pengunjung. Akibatnya semua pengunjung berbagi satu kunci, dan begitu batas tercapai seluruh situs mendapat 429.

Solusinya adalah mengonfigurasi trusted proxies agar Laravel membaca header X-Forwarded-For dari proxy yang dipercaya. Laravel 11 ke atas, di bootstrap/app.php:

Iklan
->withMiddleware(function (Middleware $middleware) {
    $middleware->trustProxies(at: [
        '10.0.0.0/8',      // contoh: IP load balancer internal
    ]);
})

Di Laravel 10, atur properti $proxies di App\Http\Middleware\TrustProxies. Hindari mempercayai semua proxy ('*') jika server Anda juga bisa diakses langsung dari internet, karena siapa pun bisa memalsukan X-Forwarded-For dan berganti "IP" sesuka hati untuk menghindari limit. Untuk Cloudflare, cara aman adalah membatasi server agar hanya menerima koneksi dari rentang IP Cloudflare, lalu mempercayai rentang tersebut. Cloudflare juga mengirim header CF-Connecting-IP berisi IP asli pengunjung.

Cara cepat mengecek: buat route sementara yang mengembalikan $request->ip(), lalu buka dari dua jaringan berbeda. Jika hasilnya sama, konfigurasi proxy Anda belum benar.

Lapisan tambahan di Nginx

Untuk menahan banjir request sebelum mencapai PHP, Nginx menyediakan limit_req:

# di blok http
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;

# di blok server / location
location /api/ {
    limit_req zone=api burst=20 nodelay;
    limit_req_status 429;
    try_files $uri $uri/ /index.php?$query_string;
}

Secara default Nginx membalas 503 untuk request yang ditolak, jadi limit_req_status 429 perlu ditambahkan. Lapisan ini melengkapi, bukan menggantikan, rate limiting di aplikasi yang memahami identitas pengguna. Konfigurasi server lengkap ada di panduan deploy Laravel ke VPS Ubuntu Nginx.

Iklan

Kesalahan umum

  • Membalas 500 atau 403 alih-alih 429, sehingga klien tidak tahu harus menunggu.
  • Tidak mengirim Retry-After, membuat klien mencoba ulang terus-menerus.
  • Memakai driver cache file di banyak server sehingga batas tidak konsisten.
  • Lupa konfigurasi trusted proxy, sehingga semua pengguna terhitung sebagai satu IP.
  • Mempercayai X-Forwarded-For dari siapa saja.
  • Batas yang sama untuk semua endpoint. Endpoint login dan kirim OTP butuh batas jauh lebih ketat daripada endpoint baca data.
  • Tidak mereset hitungan login setelah berhasil, sehingga pengguna sah ikut terkunci.

Ringkasan

  1. Tentukan endpoint sensitif (login, OTP, reset password) dan beri batas ketat.
  2. Pilih algoritma: fixed window untuk kesederhanaan, sliding window untuk akurasi, token bucket untuk burst terkendali.
  3. Balas 429 dengan Retry-After.
  4. Di Laravel, definisikan limiter dengan RateLimiter::for() dan pasang throttle:nama.
  5. Kunci per pengguna jika login, per IP jika belum, kombinasi untuk login.
  6. Gunakan cache bersama (Redis) di lingkungan multi-server.
  7. Konfigurasi trusted proxy dengan benar dan uji $request->ip().

FAQ

Algoritma apa yang dipakai middleware throttle Laravel?

Limiter bawaan Laravel bekerja seperti fixed window: penghitung disimpan di cache dengan masa kedaluwarsa yang dimulai sejak request pertama dalam jendela itu. Untuk kebanyakan aplikasi ini sudah cukup. Jika butuh perilaku sliding window atau token bucket, Anda bisa menulis logika sendiri di atas Redis.

Apakah rate limiting bisa menahan serangan DDoS?

Tidak sepenuhnya. Rate limiting di aplikasi tetap memerlukan PHP untuk menerima dan menolak request. Serangan volumetrik harus ditahan di lapisan jaringan atau CDN. Rate limiting aplikasi efektif melawan brute force dan penyalahgunaan skala kecil sampai menengah.

Kenapa API saya tiba-tiba mengembalikan 429 untuk semua pengguna?

Penyebab paling umum adalah semua request terlihat berasal dari satu IP (IP proxy atau load balancer) karena trusted proxies belum dikonfigurasi. Periksa nilai $request->ip().

Berapa batas yang ideal?

Tidak ada angka universal. Ukur pola penggunaan normal dari log, lalu tetapkan batas yang cukup longgar untuk pengguna wajar dan cukup ketat untuk menghentikan penyalahgunaan. Endpoint autentikasi biasanya dibatasi dalam hitungan satuan per menit, endpoint baca data jauh lebih longgar.

Iklan

Bagaimana menguji rate limit?

Kirim request berulang dengan loop curl dan perhatikan header-nya, misalnya for i in $(seq 1 70); do curl -s -o /dev/null -w "%{http_code}\n" https://contoh.com/api/produk; done. Di test otomatis Laravel, panggil endpoint berkali-kali lalu pastikan respons terakhir berstatus 429.

Iklan
rate limiting api laravel rate limiter throttle laravel 429 too many requests retry-after token bucket sliding window
Yudhi
Ditulis oleh

Yudhi

Web Developer

Web developer yang sehari-hari berkutat dengan PHP, Laravel, JavaScript, dan MySQL. Terbiasa membangun aplikasi web dari nol โ€” merancang database, menulis fitur, memburu bug, hingga deploy ke server โ€” lalu menuangkan solusi dan tutorialnya di DhieCoderWeb agar lebih mudah diikuti developer lain.

Bagikan artikel
Kembali

Komentar (0)

Punya pertanyaan atau tambahan? Tulis di bawah โ€” tak perlu login.

Membalas komentarโ€ฆ batal

Belum ada komentar. Jadilah yang pertama!

๐Ÿš€ Partner Recommendation

Butuh Source Code & Aplikasi Premium?

Download aplikasi Laravel, POS, Sekolah, Klinik, ERP, dan source code siap pakai di GudangCode.

GudangCode
  • โœ” Source Code Premium
  • โœ” Sistem Siap Pakai
  • โœ” Lifetime Update
  • โœ” Membership Lifetime
  • โœ” Update Aplikasi Harian
Daftar Membership โ†’
Iklan
Iklan