Home / Artikel / Web Development
Web Development

API Terbuka Bukan Berarti Bebas Dipukul: Cara Menambahkan Rate Limiting di Aplikasi Web

Endpoint API yang berjalan baik hari ini bisa kewalahan ketika dipanggil terlalu sering, entah karena bug, bot, atau penyalahgunaan. Rate limiting membantu membatasi jumlah request tanpa harus langsung menambah kapasita…

API Terbuka Bukan Berarti Bebas Dipukul: Cara Menambahkan Rate Limiting di Aplikasi Web

Sebuah API bisa tampak baik-baik saja saat hanya dipakai oleh beberapa pengguna. Masalah biasanya muncul ketika satu klien mengirim ratusan request dalam waktu singkat, baik karena tombol diklik berulang-ulang, proses otomatis yang salah konfigurasi, maupun bot yang mencoba mengeksplorasi endpoint.

Di sinilah rate limiting dibutuhkan. Sederhananya, rate limiting adalah aturan yang membatasi berapa kali sebuah identitas boleh memanggil endpoint dalam periode tertentu. Identitas itu bisa berupa alamat IP, user ID, API key, atau kombinasi beberapa faktor.

Rate limiting bukan pengganti autentikasi dan bukan solusi untuk semua serangan. Namun, mekanisme ini adalah lapisan perlindungan yang relatif sederhana untuk mengurangi beban server, mencegah penyalahgunaan ringan, dan membuat perilaku API lebih mudah diprediksi.

Mengapa pembatasan request penting?

Tanpa pembatasan, satu proses yang bermasalah dapat menghabiskan koneksi database, memenuhi worker PHP-FPM, atau membuat endpoint tertentu menjadi lambat bagi semua pengguna. Dampaknya tidak selalu terlihat sebagai serangan besar. Kadang penyebabnya hanya JavaScript yang melakukan request berulang karena kondisi retry-nya tidak pernah berhenti.

Rate limiting juga membantu melindungi endpoint yang sensitif, seperti login, pengiriman kode verifikasi, pencarian data, pembuatan laporan, dan pengiriman formulir. Endpoint semacam ini biasanya lebih mahal diproses atau memiliki risiko penyalahgunaan lebih tinggi.

Tentukan batas berdasarkan jenis endpoint

Kesalahan umum adalah menerapkan satu batas untuk seluruh API. Padahal, setiap endpoint memiliki biaya dan risiko berbeda.

  • Endpoint baca sederhana: misalnya daftar kategori atau konfigurasi publik. Batasnya bisa lebih longgar.
  • Endpoint pencarian: perlu dibatasi karena query yang kompleks dapat membebani database.
  • Endpoint login: perlu batas ketat untuk mengurangi percobaan password berulang.
  • Endpoint pengiriman email atau SMS: biasanya harus sangat ketat karena memiliki biaya langsung.
  • Endpoint unggah atau pembuatan laporan: perlu memperhitungkan ukuran file dan lama proses, bukan hanya jumlah request.

Mulailah dengan mengukur pola pemakaian normal. Jangan menentukan angka hanya berdasarkan tebakan. Jika mayoritas pengguna membutuhkan paling banyak 30 request per menit, batas 60 request per menit dapat menjadi titik awal yang masuk akal, lalu disesuaikan berdasarkan log.

Pilih identitas yang ingin dibatasi

Membatasi berdasarkan IP adalah cara paling mudah, tetapi tidak selalu adil. Banyak pengguna bisa berada di balik satu jaringan kantor, kampus, atau operator seluler. Jika batasnya terlalu kecil, semua orang di jaringan tersebut dapat ikut terkena dampak.

Untuk API yang sudah memiliki autentikasi, user ID atau API key biasanya lebih tepat. Anda juga dapat menggabungkan beberapa aturan: batas berdasarkan IP untuk pengguna anonim dan batas berdasarkan akun untuk pengguna yang sudah login.

Perlu diingat, alamat IP yang diterima aplikasi bisa dipengaruhi oleh proxy atau load balancer. Jangan langsung mempercayai header seperti X-Forwarded-For jika infrastruktur Anda belum dikonfigurasi untuk mengenali proxy yang dipercaya. Salah membaca header tersebut dapat membuat banyak pengguna dianggap berasal dari IP yang sama atau sebaliknya.

Implementasi sederhana dengan PHP dan MySQL

Untuk aplikasi kecil, rate limiting dapat dimulai dari tabel sederhana. Contohnya, simpan jumlah request dan waktu jendela pembatasan untuk setiap identitas.

CREATE TABLE api_rate_limits (
    identity_key VARCHAR(190) PRIMARY KEY,
    window_started_at DATETIME NOT NULL,
    request_count INT NOT NULL DEFAULT 0,
    updated_at DATETIME NOT NULL
);

Logika dasarnya adalah sebagai berikut: ambil catatan untuk identitas tersebut, periksa apakah jendela waktunya masih berlaku, lalu tambah jumlah request. Jika jendela sudah berakhir, hitung ulang dari nol.

$limit = 60;
$windowSeconds = 60;
$key = 'user:' . $userId;

// Ambil data berdasarkan $key dengan query terparametrisasi.
// Periksa waktu jendela dan jumlah request.
// Jika masih dalam jendela dan count >= $limit, kirim HTTP 429.
// Jika belum melewati batas, tambah count secara atomik. 'too_many_requests',
    'message' => 'Terlalu banyak permintaan. Coba lagi nanti.'
]);

Contoh tersebut sengaja tidak menyertakan seluruh kode produksi karena detail transaksi dan konkurensi sangat penting. Dua request yang datang bersamaan dapat membaca nilai count yang sama lalu menulis hasil yang salah jika prosesnya tidak dilakukan secara atomik.

Gunakan transaksi, operasi upsert, atau mekanisme penyimpanan yang memang dirancang untuk penghitung cepat. Pastikan query memiliki indeks pada identity_key dan uji perilaku ketika banyak request datang secara bersamaan.

Redis sering lebih cocok untuk penghitung sementara

Data rate limit biasanya bersifat sementara. Karena itu, Redis atau penyimpanan in-memory sejenis sering lebih sesuai daripada menulis setiap request ke tabel utama. Dengan operasi seperti INCR dan masa kedaluwarsa, penghitung dapat dibuat lebih ringan.

Namun, Redis bukan berarti bebas dari masalah. Anda tetap perlu memikirkan apa yang terjadi jika Redis tidak tersedia. Untuk endpoint biasa, aplikasi mungkin dapat menggunakan batas cadangan yang lebih longgar. Untuk endpoint sensitif seperti login atau pengiriman OTP, kegagalan penyimpanan rate limit perlu ditangani dengan lebih hati-hati.

Jangan lupa memberi respons yang berguna

Ketika batas terlampaui, gunakan status HTTP 429 Too Many Requests. Sertakan pesan yang jelas dan, bila memungkinkan, informasi kapan klien boleh mencoba lagi.

HTTP/1.1 429 Too Many Requests
Retry-After: 30
Content-Type: application/json

Header Retry-After membantu klien membuat strategi retry yang lebih tertib. Tanpa informasi tersebut, klien bisa langsung mencoba lagi dalam pola yang sama dan justru memperpanjang antrean.

Di sisi klien, hindari retry tanpa batas. Gunakan jeda bertahap, atau exponential backoff, sehingga jarak antarpercobaan semakin panjang. Untuk operasi yang tidak aman diulang, seperti membuat pesanan atau pembayaran, rate limiting harus dilengkapi idempotency agar retry tidak menghasilkan transaksi ganda.

Apa artinya bagi kita?

Rate limiting bukan sekadar menambahkan angka maksimum pada API. Ini adalah cara untuk menyatakan bahwa kapasitas sistem memiliki batas dan setiap jenis request perlu diperlakukan sesuai biayanya.

Yang bisa dilakukan sekarang:

  1. Catat jumlah request per endpoint, identitas, status respons, dan waktu pemrosesan.
  2. Pilih endpoint paling mahal atau paling sensitif untuk diberi batas terlebih dahulu.
  3. Tentukan apakah pembatasan memakai IP, user ID, API key, atau gabungan beberapa identitas.
  4. Kembalikan status 429 beserta pesan dan waktu coba ulang yang jelas.
  5. Uji request bersamaan untuk memastikan penghitung tidak mudah ditembus oleh kondisi balapan.
  6. Tinjau kembali batas setelah melihat data pemakaian nyata, bukan hanya perkiraan.

Batas yang baik tidak terasa oleh pengguna normal, tetapi segera bekerja ketika pola pemakaian mulai tidak wajar. Dengan pendekatan bertahap dan pemantauan yang rapi, rate limiting dapat menjaga API tetap responsif tanpa membuat aplikasi terasa kaku bagi pengguna yang sah.

– Rio Yotto @rioyotto