API publik mengundang dua jenis pemanggil: yang sopan dan yang lupa diri. Script yang keliru looping tanpa jeda, penjahat yang mencoba menebak token, atau integrasi yang tumbuh lebih cepat dari perkiraan — semuanya menggerus sumber daya server untuk semua orang. Rate limiting membatasi frekuensi request per identitas dalam jendela waktu tertentu, dan Laravel menyediakan mesinnya yang lengkap: class RateLimiter untuk mendefinisikan kebijakan, middleware throttle untuk menegakkannya, serta format 429 dan header standar yang bisa dibaca mesin. Chapter ini memasang semuanya pada API kita, dari limit global sampai response 429 ber-envelope kustom.

RateLimiter::for di AppServiceProvider #

Definisi limit terpusat di AppServiceProvider lewat RateLimiter::for(). Setiap limiter punya nama dan closure yang menerima request saat ini lalu mengembalikan satu atau lebih Limit:

php
<?php

// app/Providers/AppServiceProvider.php (cuplikan)

use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;

RateLimiter::for('api', function (Request $request) {
    return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
});

Bagian pentingnya ada di ->by(): itu adalah kunci bucket — penghitung limit dihitung per nilai kunci ini. Pola $request->user()?->id ?: $request->ip() berarti pengguna login dihitung per ID (semua request-nya berbagi satu bucket), sedangkan tamu dihitung per alamat IP. Tanpa by(), seluruh pengguna aplikasi berbagi satu bucket dan saling menjatuhkan.

throttleApi untuk Semua Route API #

Limiter bernama api ini diterapkan ke seluruh route API dalam satu baris konfigurasi di bootstrap/app.php:

php
<?php

// bootstrap/app.php (cuplikan withMiddleware)

->withMiddleware(function (Middleware $middleware): void {
    $middleware->throttleApi();
    ...
})

Kita buktikan pemasangannya lewat runner yang membaca isi group middleware api:

Output yang Dihasilkan:

text
isi group middleware "api" setelah throttleApi():
Array
(
    [0] => throttle:api
    [1] => Illuminate\Routing\Middleware\SubstituteBindings
)

Anggota group inilah yang otomatis melekat pada setiap route API kita — semua route yang dimuat lewat routes/api.php beserta file versi di dalamnya — sehingga limiter api dengan 60 request per menit menjadi pagar pembatas untuk seluruh permukaan API.

throttle:N,M Inline untuk Endpoint Spesifik #

Endpoint tertentu kadang butuh kebijakan lebih ketat daripada pagar global — endpoint ping kita, misalnya, tidak perlu 60 request per menit. Middleware throttle menerima format jumlah, detik tanpa perlu limiter bernama:

php
<?php

// routes/api_v1.php

use Illuminate\Support\Facades\Route;

Route::get('/ping-throttle', fn (): array => ['message' => 'pong'])
    ->middleware('throttle:10,1');

Route ini dibatasi 10 request per 1 menit (lebih ketat daripada 60 dari limiter api, jadi dialah yang menang). Kita buktikan dengan mengirim 11 request beruntun dari feature test:

Output yang Dihasilkan:

text
status 11 request beruntun ke GET /api/v1/ping-throttle:
[200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 429]

### request ke-12 → HTTP 429
body:
{
    "message": "Too Many Attempts."
}

header:
X-RateLimit-Limit: 10
X-RateLimit-Remaining: 0
Retry-After: 60
X-RateLimit-Reset: 1791921723

Header-header inilah kontrak yang dibaca client untuk mundur dengan sopan. X-RateLimit-Limit memberi tahu kuota, X-RateLimit-Remaining sisa kuota di jendela saat ini, dan Retry-After menyuruh menunggu berapa detik sebelum mencoba lagi — client yang baik mematikan loop-nya begitu melihat nilai ini. X-RateLimit-Reset berisi Unix timestamp kapan kuota kembali penuh (nilainya berbeda pada setiap eksekusi karena mengikuti waktu nyata).

Limit Berbeda untuk Tamu dan Pengguna #

Perlakuan adil tidak berarti sama rata: pengguna yang terautentikasi pantas mendapat kuota lebih besar daripada tamu anonim. Limiter uploads kita memisahkannya lewat dua cabang Limit:

php
<?php

// app/Providers/AppServiceProvider.php (cuplikan)

RateLimiter::for('uploads', function (Request $request) {
    return $request->user() !== null
        ? Limit::perMinute(10)->by($request->user()->id)
        : Limit::perMinute(2)->by($request->ip());
});

Pengguna login mendapat 10 request per menit berdasarkan ID-nya; tamu hanya 2 per menit per IP. Route POST /uploads memakai limiter ini lewat nama, dan feature test membuktikan kedua bucket benar-benar terpisah — Budi yang login tetap bisa mengunggah tepat setelah bucket IP tamu kehabisan:

Output yang Dihasilkan:

text
### POST /api/v1/uploads sebagai tamu, tiga kali beruntun
[200, 200, 429]

### POST /api/v1/uploads sebagai Budi (terautentikasi) setelah tamu diblokir
200

Alternatif ringkas untuk kasus sederhana: format pipe pada throttle inline, misalnya throttle:2|10,1, membatasi tamu 2 dan pengguna login 10 per menit tanpa limiter bernama.

Response 429 Kustom #

Body bawaan {"message": "Too Many Attempts."} berfungsi, tetapi kita ingin 429 mengikuti bentuk envelope error yang akan kita bakukan di chapter berikutnya. Method response() pada Limit mengambil alih pembentukan jawaban 429:

php
<?php

// app/Providers/AppServiceProvider.php (cuplikan)

RateLimiter::for('ping-kustom', function (Request $request) {
    return Limit::perMinute(3)->by($request->ip())
        ->response(fn (Request $request, array $headers) => response()->json([
            'error' => [
                'code' => 'rate_limited',
                'message' => 'Terlalu banyak permintaan. Coba lagi dalam 60 detik.',
            ],
        ], 429)->withHeaders($headers));
});

Closure menerima $headers — header throttle standar yang sudah dihitung framework — dan kita tempelkan kembali lewat withHeaders().

Setelah tiga request ke GET /ping-kustom (yang memakai limiter ini), request keempat dijawab dengan bentuk kustom plus header yang tetap utuh:

Output yang Dihasilkan:

text
### GET /api/v1/ping-kustom (request ke-4) → HTTP 429
{
    "error": {
        "code": "rate_limited",
        "message": "Terlalu banyak permintaan. Coba lagi dalam 60 detik."
    }
}

Retry-After: 60

Kebijakan limit kini menjaga seluruh endpoint dari penyalahgunaan frekuensi. Namun keandalan API belum lengkap tanpa kontrak error yang rapi: ketika kesalahan terjadi — entah 404, 422, atau 500 — client membutuhkan bentuk error yang bisa diprogram dan tidak membocorkan isi dalam aplikasi. Standarisasi itu adalah topik chapter API Error Handling , dengan rujukan lengkap di dokumentasi rate-limiters .