Rate limiting membatasi berapa banyak request yang boleh dikirim satu client dalam satu jangka waktu, misalnya 5 request per menit. Bayangin mesin kopi kantor yang memberi tiap karyawan 5 gelas per jam. Gelas keenam ditolak, dan layarnya bilang "coba lagi 12 menit lagi". Batasnya (5), jangka waktunya (1 jam), penolakannya, dan keterangan kapan boleh coba lagi: semuanya ada juga di API.
🧪 Tebakan cepat: batasnya 5 request per menit, dihitung per menit jam dinding (00 sampai 59 detik). Bisakah satu client mengirim 10 request yang semuanya diterima dalam 5 detik? Tebak dulu, jawabannya ada di bagian "Tiga cara menghitung".
Kenapa API butuh batas
- Menahan penyalahgunaan. Tanpa batas, orang bisa mencoba ribuan password per menit di
POST /login. - Adil untuk semua. Satu script yang memanggil API tanpa henti bisa bikin server lambat untuk semua orang.
- Biaya. Tiap request makan CPU, query database, dan kuota layanan lain yang kamu bayar.
Jawaban saat batas terlampaui: 429
Request keenam dalam satu menit dijawab begini:
HTTP/1.1 429 Too Many Requests
Retry-After: 42
RateLimit-Limit: 5
RateLimit-Remaining: 0
RateLimit-Reset: 42
Content-Type: application/json
{"error": "Too many requests, please try again later."}
429 Too Many Requestsadalah status khusus untuk "kebanyakan request". Bukan403(dilarang) atau500(server rusak).Retry-After: 42bilang ke client: tunggu 42 detik sebelum mencoba lagi.- Header
RateLimit-*ikut di setiap jawaban, termasuk yang200:RateLimit-Limitbatasnya,RateLimit-Remainingsisa request yang masih boleh,RateLimit-Resetberapa detik lagi kuotanya pulih. - Banyak API memakai nama lama
X-RateLimit-Limit,X-RateLimit-Remaining, danX-RateLimit-Reset. Hati-hati: di GitHub,X-RateLimit-Resetberisi waktu Unix (detik sejak 1970), bukan sisa detik. Baca dokumentasi API-nya.
Tiga cara menghitung
Batasnya sama: 5 request per 60 detik. Cara menghitungnya yang beda.
- Fixed window: satu penghitung per menit jam dinding. Ganti menit, penghitung kembali ke 0. Paling sederhana dan hemat memori.
- Sliding window: hitung request yang diterima dalam 60 detik terakhir, dari waktu sekarang mundur. Server menyimpan waktu tiap request.
- Token bucket: ember berisi 5 token. Tiap request mengambil 1 token, dan 1 token baru masuk tiap 12 detik (60 / 5). Ember penuh membolehkan 5 request sekaligus, lalu iramanya 1 request per 12 detik.
Coba skenario ini: 5 request di detik ke-55, lalu 5 request lagi di detik ke-60.
| Algoritma | Detik 55 (5 request) | Detik 60 (5 request) | Total diterima |
|---|---|---|---|
| Fixed window | 5 diterima (menit ke-0) | 5 diterima (menit ke-1, penghitung baru) | 10 dalam 5 detik |
| Sliding window | 5 diterima | 5 ditolak (60 detik terakhir sudah 5) | 5 |
| Token bucket | 5 diterima, ember kosong | 5 ditolak (baru terisi 5/12 token) | 5 |
Jadi bisa: dengan fixed window, 10 request diterima dalam 5 detik, dua kali lipat batasnya. Itu jawaban tebakannya. Ini kelemahan fixed window di perbatasan menit. Sliding window dan token bucket gak punya lubang itu, dengan harga lebih banyak yang harus disimpan.
Menulis limiter sendiri (fixed window)
const WINDOW_MS = 60 * 1000; // 1 menit, dalam milidetik
const LIMIT = 5;
const counters = {}; // "ip:awal menit" -> jumlah request
app.get("/api/data", (req, res) => {
const now = Date.now();
const windowStart = Math.floor(now / WINDOW_MS) * WINDOW_MS;
const key = (req.ip ?? "local") + ":" + windowStart;
counters[key] = (counters[key] ?? 0) + 1;
const resetIn = Math.ceil((windowStart + WINDOW_MS - now) / 1000);
res.set("RateLimit-Limit", String(LIMIT));
res.set("RateLimit-Remaining", String(Math.max(LIMIT - counters[key], 0)));
res.set("RateLimit-Reset", String(resetIn));
if (counters[key] > LIMIT) {
res.set("Retry-After", String(resetIn));
return res.status(429).json({ error: "Too many requests, please try again later." });
}
res.json({ data: "Hello!" });
});
windowStartadalah awal menit ini: waktu sekarang dibulatkan ke bawah ke kelipatan 60.000 ms.keymenggabungkan IP dan menitnya, jadi tiap client punya penghitung sendiri, dan menit baru otomatis mulai dari 0.resetInadalah sisa detik sampai menit berikutnya. Nilai yang sama dipakai untukRateLimit-ResetdanRetry-After.returnsebelumres.status(429)menghentikan handler, jadires.json({ data: "Hello!" })gak ikut terkirim.
| Request ke- | counters[key] |
Status | RateLimit-Remaining |
|---|---|---|---|
| 1 | 1 | 200 | 4 |
| 5 | 5 | 200 | 0 |
| 6 | 6 | 429 | 0 |
Dengan express-rate-limit
Di aplikasi Express sungguhan, kamu biasanya memakai paket express-rate-limit:
const rateLimit = require("express-rate-limit");
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 menit = 900.000 ms
limit: 100, // versi lama menulisnya max: 100
standardHeaders: "draft-6", // kirim RateLimit-Limit, -Remaining, -Reset
legacyHeaders: false, // tanpa X-RateLimit-*
});
app.use("/api/", limiter);
windowMsselalu dalam milidetik: 15 menit =15 * 60 * 1000= 900.000.limit(nama lamanyamax) adalah jumlah request per window per IP.- Yang melewati batas otomatis dapat
429dengan pesanToo many requests, please try again later.
Tester latihan gak punya paket ini, jadi di latihan kamu menulis penghitungnya sendiri seperti contoh sebelumnya.
🤔 Per IP, per user, atau per API key?
- Per IP gak butuh login, tapi satu kantor atau satu jaringan seluler bisa berbagi IP yang sama. Di balik proxy, set
app.set("trust proxy", 1)supayareq.ipberisi IP client, bukan IP proxy. - Per user atau per API key lebih adil untuk endpoint yang butuh login.
- Login sebaiknya dibatasi per email dan per IP, karena penyerang bisa berganti IP.
- Penghitung di memori hilang saat server restart dan gak terbagi antar server. Kalau server-nya lebih dari satu, simpan di Redis.
⚠️ Jebakan yang sering ketemu
- Lupa
returnsetelah429. Handler lanjut jalan dan mengirim jawaban kedua. windowMsdalam detik.windowMs: 60artinya 60 milidetik, bukan 1 menit. Tulis60 * 1000.- Satu penghitung untuk semua client. Tanpa IP di
key, satu client yang rakus memblokir semua orang. - Client yang mengabaikan
Retry-After. Mencoba ulang terus-menerus cuma menambah429. Tunggu sesuai angka yang diberikan.
TL;DR: Rate limiting membatasi request per client per jangka waktu. Yang melewati batas dijawab 429 Too Many Requests dengan Retry-After (detik menunggu). RateLimit-Limit, RateLimit-Remaining, dan RateLimit-Reset (sisa detik) ikut di setiap jawaban; nama lamanya X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset. Fixed window paling sederhana tapi bisa meloloskan 2x batas di perbatasan menit; sliding window menghitung 60 detik terakhir; token bucket membolehkan ledakan sebesar ember lalu irama tetap. Di express-rate-limit, windowMs dalam milidetik (15 * 60 * 1000 = 900.000) dan limit (dulu max) jumlah request-nya. Pisahkan penghitung per IP atau per user, dan return setelah 429.