Rate Limiting pada Backend: Cara Membatasi Permintaan Tanpa Mengganggu Pengguna

Rate limiting backend adalah cara membatasi permintaan yang masuk ke server dalam periode waktu tertentu. Tujuannya melindungi aplikasi dari lonjakan trafik, bot, dan percobaan peretasan. Namun, pembatasan yang asal-asalan justru bisa menolak pengguna sungguhan yang tidak melakukan kesalahan apa pun.

Bayangkan seorang pelanggan sedang membuka dashboard. Halaman itu mengirim belasan permintaan sekaligus, lalu tiba-tiba muncul pesan error. Pelanggan mengira aplikasi Anda rusak, padahal server hanya menjalankan aturan yang terlalu kaku. Karena itu, tantangan sebenarnya bukan sekadar memblokir, melainkan memblokir dengan cerdas.

Artikel ini membahas apa itu rate limiting backend, alasan pembatasan bisa mengganggu pengguna sah, lima prinsip yang lebih ramah, dan contoh kode di Express. Selain itu, Anda akan menemukan kesalahan umum dan daftar periksa sebelum rilis.

Ilustrasi rate limiting backend yang membatasi permintaan dengan ramah pengguna

Daftar Isi

Apa Itu Rate Limiting Backend dan Mengapa Dibutuhkan?

Rate limiting backend adalah mekanisme di sisi server yang menghitung jumlah permintaan dari satu klien, lalu menolak permintaan berlebih. Server biasanya menjawab dengan status HTTP 429. Menurut dokumentasi MDN tentang status 429, respons ini menandakan klien mengirim terlalu banyak permintaan dalam waktu tertentu, dan server boleh menyertakan header Retry-After.

Ada beberapa alasan mengapa fitur ini penting:

  • Melindungi server. Satu klien yang agresif tidak boleh menghabiskan sumber daya untuk semua orang.
  • Menahan serangan tebak kata sandi. Endpoint login yang dibiarkan terbuka mudah diserang berulang kali.
  • Mengurangi scraping dan bot. Pengambilan data massal membebani server tanpa memberi nilai bagi pengguna.
  • Menahan dampak bug klien. Kode front-end yang masuk ke perulangan tak terbatas bisa membanjiri API Anda sendiri.
  • Mengendalikan biaya. Setiap permintaan memakai CPU, database, atau layanan pihak ketiga yang berbayar.

Mengapa Membatasi Permintaan Bisa Mengganggu Pengguna Sah?

Aturan yang tampak aman di atas kertas sering bermasalah di dunia nyata. Pertama, banyak pengguna berbagi satu alamat IP publik, misalnya di kantor, kampus, atau jaringan seluler. Jika Anda membatasi hanya berdasarkan IP, satu orang yang agresif bisa memblokir ratusan orang lain.

Kedua, satu halaman web sering memuat banyak data sekaligus. Batas yang terlalu kecil membuat halaman gagal tampil walaupun pengguna hanya membukanya satu kali. Ketiga, lonjakan wajar bisa terjadi saat promo atau peluncuran fitur. Aturan yang tidak fleksibel akan menganggap lonjakan itu sebagai serangan.

Terakhir, pesan error yang membingungkan memperburuk keadaan. Pengguna tidak tahu apakah mereka salah, aplikasinya rusak, atau koneksinya bermasalah. Akibatnya, mereka menekan tombol berulang kali dan memperparah beban server.

Baca juga: Rate Limiting API: 4 Algoritma dan Cara Pakainya di Express

5 Prinsip Rate Limiting Backend yang Ramah Pengguna

Kelima prinsip berikut membantu Anda membatasi permintaan tanpa menghukum pengguna yang tidak bersalah.

1. Kenali Pengguna, Bukan Hanya Alamat IP

Gunakan identitas yang paling spesifik untuk menyusun kunci pembatasan. Untuk pengguna yang sudah masuk, pakai ID pengguna. Untuk klien API, pakai API key. Baru jika keduanya tidak ada, gunakan alamat IP sebagai cadangan.

Selain itu, perhatikan konfigurasi proxy. Jika aplikasi berjalan di belakang reverse proxy atau load balancer, Express perlu pengaturan trust proxy agar req.ip menunjukkan alamat klien asli. Tanpa pengaturan itu, semua pengguna akan terlihat berasal dari alamat proxy yang sama. Identitas sesi yang berasal dari cookie juga bisa dipakai, dan perbedaannya dengan penyimpanan lain dijelaskan di Local Storage, Session Storage, Cookies: 7 Perbedaan.

2. Bedakan Batas untuk Setiap Endpoint

Tidak semua endpoint memiliki risiko yang sama. Endpoint login atau pengiriman kode OTP perlu batas ketat. Sebaliknya, endpoint untuk membaca data publik bisa lebih longgar. Tabel berikut memberi contoh angka awal, yang harus Anda sesuaikan dengan data trafik sebenarnya.

EndpointContoh batas awalAlasan
Login10 percobaan per 15 menitMenahan tebak kata sandi
Kirim OTP atau reset sandi3 sampai 5 per jamMencegah penyalahgunaan dan biaya pesan
Pencarian30 per menitQuery pencarian cukup mahal
Baca data umum120 per menitRingan dan sering dipakai
Unggah berkasKetat, per ukuran dan jumlahMemakai bandwidth dan penyimpanan

Jika Anda memakai GraphQL, satu permintaan bisa sangat berat. Karena itu, batasi berdasarkan kompleksitas query, bukan hanya jumlah permintaan. Perbandingan kedua pendekatan ada di REST API vs GraphQL: Mana yang Lebih Cocok untuk Proyek Web Modern?.

3. Izinkan Lonjakan Singkat

Pengguna manusia tidak mengirim permintaan dengan ritme yang rapi. Mereka membuka halaman, lalu beberapa permintaan muncul bersamaan. Setelah itu, mereka diam beberapa detik. Pola ini wajar dan seharusnya tidak ditolak.

Algoritma seperti token bucket dan sliding window mampu menampung lonjakan singkat sambil tetap menjaga rata-rata jangka panjang. Penjelasan setiap algoritma ada di artikel Rate Limiting API: 4 Algoritma dan Cara Pakainya di Express. Di sini, cukup ingat satu hal: pilih algoritma yang cocok dengan perilaku pengguna Anda.

4. Beri Tahu Pengguna dengan Respons 429 yang Jelas

Saat menolak permintaan, kirim informasi yang berguna. Gunakan status 429, bukan 403 atau 500, karena maknanya paling tepat. Sertakan header Retry-After supaya klien tahu kapan boleh mencoba lagi. Selain itu, sertakan pesan yang mudah dibaca manusia.

Banyak API juga mengirim header sisa kuota, seperti RateLimit-Limit, RateLimit-Remaining, dan RateLimit-Reset. Nama header ini berasal dari draf standar IETF yang masih berkembang, sedangkan sebagian layanan memakai awalan X-RateLimit-. Pilih satu format, lalu dokumentasikan secara konsisten.

5. Siapkan Klien agar Mundur dengan Sopan

Rate limiting bekerja paling baik jika klien ikut bekerja sama. Klien perlu membaca Retry-After, menunggu, lalu mencoba lagi dengan jeda yang bertambah. Teknik ini disebut exponential backoff. Contoh berikut menunjukkan versi sederhananya di JavaScript.

async function fetchWithRetry(url, options = {}, maxRetry = 3) {
  for (let attempt = 0; attempt <= maxRetry; attempt++) {
    const res = await fetch(url, options);

    if (res.status !== 429) return res;

    const retryAfter = Number(res.headers.get('Retry-After'));
    const waitSeconds = retryAfter || 2 ** attempt;
    const jitter = Math.random() * 500;

    await new Promise((resolve) =>
      setTimeout(resolve, waitSeconds * 1000 + jitter)
    );
  }

  throw new Error('Terlalu banyak permintaan. Coba lagi nanti.');
}

Variabel jitter menambahkan jeda acak kecil. Dengan begitu, ribuan klien tidak mencoba ulang pada detik yang sama. Di antarmuka, tampilkan pesan seperti “Coba lagi dalam 30 detik” dan nonaktifkan tombol sementara. Teknik sisi klien seperti debounce dan throttle juga mengurangi permintaan yang tidak perlu, seperti dibahas di Debounce vs Throttle JavaScript: Perbedaan dan Contoh Mudah. Namun, teknik itu tidak menggantikan rate limiting backend, karena klien selalu bisa dilewati.

Alur respons 429 Too Many Requests pada rate limiting backend dengan header Retry-After
.

Contoh Cara Membatasi Permintaan di Express

Kode berikut adalah middleware sederhana untuk memperlihatkan idenya. Middleware ini memakai penghitung jendela waktu tetap yang disimpan di memori. Tujuannya mengilustrasikan kunci per pengguna, header, dan respons 429, bukan menjadi solusi produksi.

function rateLimit({ windowMs, limit, keyFn }) {
  const buckets = new Map();

  // Bersihkan data kedaluwarsa agar memori tidak membengkak
  setInterval(() => {
    const now = Date.now();
    for (const [key, entry] of buckets) {
      if (now >= entry.resetAt) buckets.delete(key);
    }
  }, 60_000).unref();

  return (req, res, next) => {
    const key = keyFn(req);
    const now = Date.now();
    let entry = buckets.get(key);

    if (!entry || now >= entry.resetAt) {
      entry = { count: 0, resetAt: now + windowMs };
      buckets.set(key, entry);
    }

    entry.count += 1;

    const remaining = Math.max(0, limit - entry.count);
    const resetSeconds = Math.ceil((entry.resetAt - now) / 1000);

    res.set({
      'RateLimit-Limit': String(limit),
      'RateLimit-Remaining': String(remaining),
      'RateLimit-Reset': String(resetSeconds),
    });

    if (entry.count > limit) {
      res.set('Retry-After', String(resetSeconds));
      return res.status(429).json({
        error: 'terlalu_banyak_permintaan',
        message: 'Anda terlalu sering mengirim permintaan. Coba lagi sebentar lagi.',
        retryAfterSeconds: resetSeconds,
      });
    }

    next();
  };
}

Setelah middleware siap, pasang dengan batas yang berbeda untuk tiap kebutuhan. Pastikan express.json() sudah terpasang sebelum memakai req.body pada kunci login.

const express = require('express');
const app = express();

app.set('trust proxy', 1); // sesuaikan dengan jumlah proxy di depan aplikasi
app.use(express.json());

// Prioritaskan ID pengguna, lalu pakai IP sebagai cadangan
const byUserOrIp = (req) => req.user?.id ?? req.ip;

// Batas umum untuk seluruh API
app.use('/api', rateLimit({
  windowMs: 60_000,
  limit: 120,
  keyFn: byUserOrIp,
}));

// Batas lebih ketat untuk login, per kombinasi IP dan email
app.post('/api/login', rateLimit({
  windowMs: 15 * 60_000,
  limit: 10,
  keyFn: (req) => `login:${req.ip}:${req.body?.email ?? ''}`,
}), loginHandler);

Angka batas sebaiknya tidak ditulis mati di dalam kode. Simpan nilai tersebut sebagai konfigurasi, misalnya lewat file .env, sehingga Anda bisa menyesuaikannya tanpa mengubah kode. Panduannya ada di Environment Variable: Cara Pakai .env di Node.js.

Rate Limiting Backend pada Skala Besar: Kapan Perlu Redis?

Penghitung di memori hanya cocok untuk satu proses. Jika aplikasi berjalan di beberapa instance, setiap instance menghitung sendiri-sendiri. Akibatnya, batas efektif menjadi batas per instance dikalikan jumlah instance. Selain itu, penghitung hilang setiap kali aplikasi restart.

Solusinya adalah penyimpanan bersama, dan Redis menjadi pilihan populer karena mendukung operasi atomik dan masa berlaku data. Cara lain adalah memindahkan pembatasan ke lapisan di depan aplikasi, seperti reverse proxy atau API gateway. Namun, aturan yang bergantung pada identitas pengguna biasanya tetap lebih mudah ditangani di aplikasi.

Apa pun pilihan Anda, pantau hasilnya. Persentase respons 429 yang tinggi pada pengguna sah menandakan batas terlalu ketat. Sebaliknya, lonjakan trafik tanpa satu pun respons 429 bisa berarti batas terlalu longgar.

Kesalahan Umum saat Membatasi Permintaan

  • Hanya memakai IP. Pengguna di jaringan yang sama saling menghambat.
  • Lupa pengaturan proxy. Semua pengguna tampak berasal dari satu alamat yang sama.
  • Tanpa Retry-After. Klien tidak tahu kapan boleh mencoba lagi, sehingga terus mengetuk server.
  • Pesan error yang kabur. Pengguna mengira aplikasi rusak.
  • Memblokir permanen. Pengguna sah yang sekali terkena batas bisa terkunci selamanya.
  • Tidak memantau data. Batas ditentukan sekali, lalu tidak pernah ditinjau.
  • Mengandalkan klien saja. Pembatasan di front-end mudah dilewati.
Diagram membatasi permintaan per pengguna, per endpoint, dan lonjakan singkat

Daftar Periksa Rate Limiting Backend sebelum Rilis

  • Apakah kunci pembatasan memakai ID pengguna atau API key sebelum IP?
  • Apakah pengaturan proxy sudah benar sehingga IP klien terbaca akurat?
  • Apakah endpoint sensitif punya batas yang lebih ketat?
  • Apakah lonjakan singkat yang wajar masih diizinkan?
  • Apakah respons 429 menyertakan Retry-After dan pesan yang jelas?
  • Apakah klien membaca pesan itu dan menunggu dengan backoff?
  • Apakah penyimpanan penghitung sudah sesuai jumlah instance?
  • Apakah respons 429 dipantau dan batasnya bisa diubah lewat konfigurasi?

Kesimpulan: Rate Limiting Backend yang Baik Melindungi Server dan Pengguna

Rate limiting backend bukan sekadar tembok yang menolak semua permintaan berlebih. Pembatasan yang baik mengenali pengguna, membedakan endpoint, menerima lonjakan wajar, dan menjelaskan alasan penolakan dengan jelas. Dengan begitu, Anda tetap melindungi server tanpa membuat pengguna sah merasa dihukum.

Mulailah dari langkah kecil. Pasang batas umum, perketat endpoint sensitif, lalu pantau respons 429. Setelah itu, sesuaikan angka berdasarkan data nyata. Cara membatasi permintaan yang tepat akan terus berkembang seiring pertumbuhan aplikasi Anda.

Artikel Lainnya di NgodingDaily

Lanjutkan belajar dengan topik lain yang saling melengkapi:

Leave a Reply

Your email address will not be published. Required fields are marked *