Rate Limiting dan Throttling di Express.js: Melindungi API dari Abuse

Lhuqita Fazry
Web Development Express.js Rate Limiting API Security Node.js
Rate Limiting dan Throttling di Express.js: Melindungi API dari Abuse

Setiap endpoint API yang terbuka ke publik adalah pintu masuk potensial untuk traffic yang tidak terduga. Tanpa mekanisme pembatasan, sebuah bot bisa mengirimkan ratusan request per detik — membanjiri database, menghabiskan resource server, dan mengorbankan pengalaman pengguna yang sah. Rate limiting dan throttling adalah dua strategi fundamental yang perlu kita terapkan sebelum membuka akses API ke dunia luar.

Rate limiting membatasi jumlah request dalam periode waktu tertentu. Jika batasnya 100 request per menit, request ke-101 akan ditolak dengan status 429. Throttling bekerja secara berbeda: request yang melebihi kapasitas tidak langsung ditolak, tetapi diantrekan atau ditunda agar diproses secara bertahap. Kedua pendekatan ini bisa dikombinasikan untuk proteksi berlapis — rate limiting sebagai gerbang pertama, throttling sebagai pengatur arus di baliknya.

Implementasi Rate Limiting dengan express-rate-limit

Library express-rate-limit adalah solusi paling umum untuk menambahkan rate limiting pada aplikasi Express.js. Konfigurasinya berpusat pada dua parameter utama: windowMs menentukan jendela waktu pengukuran, dan max menentukan jumlah request maksimum dalam jendela tersebut. Setiap IP address mendapat jatah terpisah, sehingga satu pengguna tidak mengurangi kuota pengguna lain.

javascriptjavascript
const express = require('express');
const rateLimit = require('express-rate-limit');

const app = express();

// Rate limiter global — 100 request per 15 menit per IP
const globalLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 100,
  message: {
    error: 'Terlalu banyak request dari IP ini. Coba lagi dalam 15 menit.'
  },
  standardHeaders: true,
  legacyHeaders: false
});

app.use(globalLimiter);

// Rate limiter khusus untuk endpoint login — 5 attempt per 15 menit
const loginLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 5,
  message: {
    error: 'Terlalu banyak percobaan login. Coba lagi dalam 15 menit.'
  },
  keyGenerator: (req) => {
    return req.body.email || req.ip;
  }
});

app.post('/api/login', loginLimiter, (req, res) => {
  // Logic autentikasi di sini
  res.json({ message: 'Login berhasil' });
});

app.listen(3000, () => {
  console.log('Server berjalan di port 3000');
});

Konfigurasi di atas menerapkan dua layer perlindungan. Layer pertama membatasi seluruh endpoint ke 100 request per 15 menit per IP address, cukup untuk penggunaan normal namun cukup ketat untuk menghambat bot. Layer kedua menargetkan endpoint /api/login secara spesifik dengan batas lebih ketat: hanya 5 percobaan per 15 menit, menggunakan email sebagai key agar satu akun tidak bisa di-brute force dari IP yang berbeda.

Opsi standardHeaders: true secara otomatis menambahkan header RateLimit-Policy dan RateLimit-Remaining pada response sesuai RFC terbaru, sedangkan legacyHeaders: false menghapus header X-RateLimit-* yang sudah deprecated. Kombinasi ini memastikan kompatibilitas ke depan sekaligus membersihkan response dari header usang.

Untuk endpoint lain yang sensitif seperti password reset, user registration, dan search, kita bisa membuat limiter terpisah dengan batas yang disesuaikan. Prinsipnya sederhana: semakin tinggi risiko abasenya, semakin ketat batasnya.

Throttling Request dengan Token Bucket Pattern

Rate limiting menolak request yang melebihi batas — throttling mengaturnya agar tetap diproses, hanya dengan delay yang disesuaikan. Token Bucket adalah algoritma yang paling umum digunakan untuk throttling. Cara kerjanya sederhana: bucket memiliki kapasitas tetap (burst size), token diisi ulang pada rate konstan (refill rate), dan setiap request mengonsumsi satu token. Jika bucket kosong, request harus menunggu sampai token tersedia. Keunggulan Token Bucket dibanding algoritma lain adalah kemampuannya mengizinkan burst pendek — traffic bisa melebihi rata-rata sesaat selama token masih tersedia.

Library bottleneck mengimplementasikan Token Bucket pattern di Node.js dan cocok untuk mengatur concurrency pada endpoint yang berat secara komputasi, seperti endpoint yang memanggil external API atau menjalankan query database kompleks.

javascriptjavascript
const Bottleneck = require('bottleneck');

// Maksimal 3 request konkuren, 10 request per detik
const limiter = new Bottleneck({
  maxConcurrent: 3,
  minTime: 100 // 100ms antar request = 10 per detik
});

async function procesDataTask(data) {
  // Simulasi task berat — database query atau external API call
  await new Promise(resolve => setTimeout(resolve, 500));
  return { processed: true, id: data.id };
}

app.post('/api/data', async (req, res) => {
  try {
    const result = await limiter.schedule(() =>
      procesDataTask(req.body)
    );
    res.json(result);
  } catch (err) {
    res.status(500).json({ error: 'Task gagal diproses' });
  }
});
Diagram algoritma Leaky Bucket untuk rate limiting — request masuk ke bucket dan mengalir keluar pada rate konstan
MERN Stack Development
Web App • Beginner

MERN Stack Development

Launch your journey into full-stack web development with this comprehensive, pro...

Daftar

Gambar: Alur request melalui Leaky Bucket algorithm — request diproses pada rate konstan, kelebihan ditolak — Sumber: [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Leaky_bucket_as_a_meter-policing.JPG)

Perbedaan mendasar antara fixed window, sliding window, dan token bucket terletak pada granularitas dan perilaku di batas window. Fixed window (express-rate-limit) menerapkan batas dalam blok waktu tetap — 100 request per 15 menit berarti counter direset di menit ke-0, 15, 30, dst. Masalah yang muncul: di batas window, bisa terjadi burst 200 request dalam 2 detik (100 di akhir window pertama, 100 di awal window kedua). Sliding window menghitung request dalam window yang bergeser terus-menerus, menghindari masalah ini. Token bucket mengizinkan burst pendek sebagai pengecualian selama token masih tersedia, membuat throttling lebih smooth dibanding hard reject.

Throttling lebih tepat daripada rate limiting ketika kita memproses task berat seperti batch processing, polling data, atau streaming — scenario di mana menolak request secara langsung lebih merugikan daripada mengantrekannya. Pada endpoint yang memanggil external API dengan rate limit sendiri (seperti Stripe, GitHub API), throttling memastikan kita tidak melebihi kuota tanpa membuang request yang sudah diterima.

Strategi Response dan Error Handling untuk Rate-Limited Request

Saat request ditolak karena rate limiting, client perlu tahu kapan bisa mencoba lagi. HTTP status code 429 Too Many Requests adalah respons standar, dilengkapi dengan header Retry-After yang memberikan estimasi waktu tunggu dalam detik. Tanpa informasi ini, client hanya bisa meneka-neka kapan harus retry, yang sering berujung pada polling agresif yang memperburuk situasi.

javascriptjavascript
const rateLimit = require('express-rate-limit');

const apiLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 100,
  handler: (req, res) => {
    const retryAfter = Math.ceil(req.rateLimit.resetTime / 1000);
    res.set('Retry-After', retryAfter);
    res.set('X-RateLimit-Limit', req.rateLimit.limit);
    res.set('X-RateLimit-Remaining', '0');
    res.set('X-RateLimit-Reset', req.rateLimit.resetTime);

    res.status(429).json({
      error: 'Rate limit terlampaui',
      retryAfter: `${retryAfter} detik`,
      limit: req.rateLimit.limit
    });
  }
});

app.use('/api/', apiLimiter);

Custom handler di atas mengganti response default dengan format yang lebih informatif. Header Retry-After memberitahu client berapa lama harus menunggu sebelum mencoba lagi — header ini dihormati oleh HTTP client library dan crawler. Header X-RateLimit-Limit, X-RateLimit-Remaining, dan X-RateLimit-Reset memungkinkan client untuk memantau penggunaan rate limit secara proaktif dan mengimplementasikan progressive backoff sebelum batas tercapai.

Di sisi client, pattern yang disarankan adalah membaca header rate limit dari setiap response untuk menentukan kapan harus memperlambat request. Jika X-RateLimit-Remaining mendekati nol, client sebaiknya menambah delay antar request secara bertahap, bukan menunggu sampai menerima 429. Pendekatan ini disebut adaptive rate limiting — client menyesuaikan kecepatan request berdasarkan informasi dari server, bukan bereaksi setelah ditolak.

Untuk logging dan monitoring, setiap request yang ter-limit sebaiknya dicatat dengan detail IP, endpoint, dan timestamp. Data ini membantu mendeteksi pattern serangan — misalnya, jika satu IP konsisten menembus batas pada endpoint login, itu indikasi credential stuffing yang perlu diblokir secara permanen. Tools seperti Winston atau Pino bisa dikombinasikan dengan alert system untuk notifikasi real-time saat anomali terdeteksi.

Kombinasi Rate Limiting, Throttling, dan Reverse Proxy untuk Proteksi Berlapis

Arsitektur defense-in-depth menerapkan pembatasan di beberapa layer sekaligus: rate limiting di application layer (Express.js), throttling di service layer (Bottleneck), dan request limiting di reverse proxy (Nginx atau Cloudflare). Setiap layer menangkap traffic yang lolos dari layer sebelumnya. Jika Nginx menolak request karena limit terlampaui, request tersebut tidak pernah mencapai Node.js — menghemat CPU dan memory.

Konfigurasi Nginx sebagai first line of defense sebelum traffic mencapai Node.js:

nginxnginx
# /etc/nginx/conf.d/rate-limit.conf

# Definisikan zona — 10MB menyimpan ~160.000 IP address
# Batas: 30 request per menit per IP
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=30r/m;

server {
    listen 80;
    server_name api.example.com;

    location /api/ {
        # Terapkan rate limit dengan burst 10 dan nodelay
        limit_req zone=api_limit burst=10 nodelay;

        proxy_pass http://127.0.0.1:3000;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $host;
    }
}

Konfigurasi Nginx di atas membatasi 30 request per menit per IP address, dengan burst tolerance sebanyak 10 request. Parameter nodelay berarti burst request diproses secepat mungkin, bukan di-queue. Ketika Nginx sudah memblokir request berlebih, traffic yang sampai ke Express.js sudah terfilter, mengurangi beban pada application layer secara signifikan — dalam praktik, Nginx bisa menolak ribuan request per detik tanpa membebani Node.js.

Satu detail penting yang sering terlewat: saat aplikasi berjalan di belakang load balancer atau reverse proxy, IP address yang terlihat oleh Express.js adalah IP proxy, bukan IP client asli. Tanpa konfigurasi tambahan, req.ip akan berisi IP internal proxy, membuat rate limiting tidak efektif karena semua request tampak berasal dari IP yang sama. Kita perlu mengaktifkan trust proxy agar express-rate-limit membaca header X-Forwarded-For sebagai IP client.

javascriptjavascript
// Saat di belakang Nginx atau load balancer
app.set('trust proxy', 1);

Nilai 1 berarti trust proxy pertama (biasanya Nginx atau load balancer). Konfigurasi ini memastikan req.ip berisi IP address client yang sebenarnya, bukan IP internal proxy. Tanpa setting ini, rate limiting akan membatasi semua user seolah mereka berasal dari satu IP yang sama — sebuah kesalahan kritis yang membuat perlindungan menjadi sia-sia.

Checklist implementasi bertahap dari simple ke production-grade:

1. Tambahkan express-rate-limit global — 100 request per 15 menit per IP. 2. Terapkan limiter ketat pada endpoint sensitif (login, password reset). 3. Integrasikan bottleneck untuk throttling pada endpoint berat. 4. Konfigurasi Nginx limit_req_zone sebagai first line of defense. 5. Aktifkan trust proxy saat berjalan di belakang load balancer. 6. Monitor request yang ditolak untuk mendeteksi pattern serangan dan sesuaikan batas.

Setiap langkah di atas bisa diimplementasikan secara independen — mulai dari yang paling sederhana (global rate limiter) dan tambahkan layer perlindungan secara bertahap. Tidak perlu menerapkan semuanya sekaligus; yang penting adalah memiliki minimal satu lapisan perlindungan sebelum membuka API ke publik.

Mau memperdalam skill backend engineering dan API security? Bergabunglah dengan Web Development Bootcamp di Rumah Coding. Kurikulum praktis mencakup Express.js, database integration, dan deployment strategi yang siap production.

Course Terkait

EduStream - Mini Learning Management System (LMS)
Premium Course Web App

MERN Stack Development

Launch your journey into full-stack web development with this comprehensive, project-driven course. Designed for beginners, this course demystifies the MERN stack (MongoDB, Express.js, React.js, Node.js) by guiding you step-by-step in building a real-world application from scratch. By the end of this course, you will have the practical skills and a complete portfolio project to confidently step into the modern web development industry.

Capstone Project

EduStream - Mini Learning Management System (LMS)

  • Secure Authentication & Authorization: Robust user registration and login using JWT, with strict role-based access control (Admin/Instructor vs. Student).
  • Course Management (Admin Dashboard): Full CRUD (Create, Read, Update, Delete) capabilities for administrators to manage course details, including titles, descriptions, pricing, and thumbnail image uploads.
  • Public Course Catalog: An interactive and responsive storefront where users can browse available courses.
7 Weeks Beginner
Lihat Detail Course

Artikel Terkait