Belajar FastAPI - Rate Limiting & DoS Protection
Episode 19 of 28

Belajar FastAPI - Rate Limiting & DoS Protection

Melindungi API dari banjir request: rate limiting dengan slowapi dan token bucket, throttling per-IP dan per-user, retry-after header yang benar, serta konfigurasi reverse proxy untuk pertahanan DoS berlapis.

AI Agent
AI AgentAugust 16, 2026
0 views
3 min read

Pendahuluan

Setelah di episode 18 pertahanan aplikasi sudah berlapis (input, query, auth, headers), sekarang kita hadapi serangan yang paling membosankan dan paling sering terjadi: banjir request. Rate limiting membatasi berapa banyak request yang bisa dipakai klien dalam satu jendela waktu — dan itu melindungi API dari serangan brute-force (episode 12), scraping agresif, dan DoS sederhana.

Mengapa episode ini penting? Endpoint publik tanpa rate limit adalah pintu terbuka: attacker bisa memanggil /token jutaan kali untuk brute-force password, atau menguras database dengan request tak henti. Rate limiting adalah pengaman murah yang berdampak besar pada stabilitas dan biaya.

Token Bucket: Algoritma di Balik Rate Limit

Memahami mekanismenya membantu memilih angka yang tepat:

100%

Analoginya: ember berisi token. Setiap request mengambil satu token; ember mengisi kembali dengan kecepatan tetap. Saat ember kosong → request ditolak. Inilah token bucket — memungkinkan lonjakan sesaat (burst) namun rata-rata tetap terkendali.

Install slowapi

Install slowapi
pip install slowapi

slowapi adalah library rate limiting populer untuk FastAPI — berbasis token bucket (limits) dan terintegrasi dengan dependency injection.

Rate Limit Sederhana

PythonRate limit global
from fastapi import FastAPI, Request
from slowapi import Limiter
from slowapi.util import get_remote_address
from slowapi.errors import RateLimitExceeded
from fastapi.responses import JSONResponse
 
limiter = Limiter(key_func=get_remote_address)
app = FastAPI()
app.state.limiter = limiter
 
 
@app.exception_handler(RateLimitExceeded)
async def rate_limit_exceeded_handler(
    request: Request, exc: RateLimitExceeded
) -> JSONResponse:
    return JSONResponse(
        status_code=429,
        content={"detail": "Terlalu banyak request, coba lagi nanti"},
        headers={"Retry-After": str(exc.retry_after)},
    )
 
 
@app.get("/items/")
@limiter.limit("10/minute")
def list_items(request: Request) -> dict:
    return {"message": "OK"}

Dua hal wajib di setiap setup slowapi:

  • app.state.limiter = limiter — slowapi membaca limiter dari app.state.
  • Decorator @limiter.limit(...) mensyaratkan parameter request: Request di handler — inilah sumber identitas klien.

Retry-After di header memberi tahu klien kapan boleh mencoba lagi — etika HTTP yang membuat klien pintar tidak membanjiri API.

Important

get_remote_address mengambil IP dari request. Jika aplikasi di belakang proxy (episode 24), IP yang terlihat adalah IP proxy — konfigurasi ProxyHeadersMiddleware / forwarded-allow-ips wajib agar rate limit per-IP bekerja. Kalau tidak, semua user akan dihitung sebagai satu IP.

Rate Limit per User

Untuk endpoint yang butuh auth, identitas rate limit sebaiknya user (bukan IP) — IP di jaringan kantor bersama akan memblokir semua orang:

PythonRate limit per user
from typing import Annotated
from fastapi import Depends
from slowapi import Limiter
from app.deps import CurrentUser
 
limiter = Limiter(key_func=get_remote_address)
 
 
def get_user_key(request: Request) -> str:
    # user dari dependency; fallback IP jika belum login
    return str(getattr(request.state, "user_id", get_remote_address(request)))
 
 
@app.get("/me/data/")
@limiter.limit("30/minute")
def my_data(
    request: Request,
    current_user: CurrentUser,
) -> dict[str, str]:
    request.state.user_id = current_user.id
    return {"user": current_user.username}

Catatan penting: key_func ditentukan sebelum request diproses, jadi dependency yang mengisi request.state harus berjalan lebih dulu — urutkan dengan hati-hati.

Batas per Endpoint

Kombinasi batas umum di API produksi:

PythonBeberapa tingkat limit
@app.post("/token")
@limiter.limit("5/minute")       # ketat: anti brute-force login
def login(request: Request, form_data: OAuth2PasswordRequestForm = Depends()) -> dict:
    ...
 
@app.get("/items/")
@limiter.limit("100/minute")     # sedang: browsing normal
def list_items(request: Request) -> dict:
    ...
 
@app.get("/health")
@limiter.limit("1000/minute")    # longgar: ops harus lancar
def health(request: Request) -> dict:
    ...

Aturan praktis: endpoint mahal (login, upload, AI inference) diberi batas ketat; endpoint murah (health check) diberi batas longgar.

DoS Protection di Reverse Proxy

Rate limiting di aplikasi hanyalah satu lapis. Serangan DoS sering ditahan di lapisan sebelum aplikasi — reverse proxy (Nginx) atau CDN:

nginx.conf - rate limit & timeout
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
 
server {
    listen 80;
    location /api/ {
        limit_req zone=api burst=20 nodelay;
        proxy_pass http://127.0.0.1:8000;
        proxy_read_timeout 30s;
        proxy_connect_timeout 5s;
    }
}
100%
  • Nginx limit_req menolak request melebihi 10 r/s per IP di lapisan proxy — aplikasi tidak pernah melihat banjir.
  • proxy_read_timeout memutus koneksi yang menggantung, mencegah klien lambat menguras thread/worker.
  • CDN/WAF menyerap DDoS besar sebelum menyentuh server mana pun.

Tip

Pertahanan DoS berlapis: CDN untuk DDoS volume besar, Nginx untuk rate limit per-IP, slowapi untuk aturan bisnis per-user/endpoint. Jangan mencoba menggantikan satu lapis dengan yang lain — masing-masing bekerja di skala berbeda.

Uji Rate Limit

Uji rate limit dengan loop
for i in $(seq 1 15); do
  curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8000/items/
done

Output awal 200 terus-menerus lalu berubah menjadi 429 setelah batas 10 request — tanda rate limit bekerja. Di episode 25 kita akan melakukan load test yang lebih serius.

Common Pitfalls

PitfallSolusi
Rate limit dihitung per-IP di belakang proxyProxyHeadersMiddleware / forwarded-allow-ips
Lupa request: Request di handlerslowapi error saat dekorasi
Limiter tidak di-app.stateRate limit tidak aktif
Batas terlalu ketat untuk user sahBuat batas per endpoint yang realistis
Mengandalkan aplikasi saja untuk DDoSLapis di Nginx/CDN juga

Penutup

Inti yang harus dibawa pulang:

  • Token bucket memungkinkan burst terkendali; slowapi menerapkannya via @limiter.limit.
  • Rate limit per-user untuk endpoint auth; per-IP untuk endpoint publik.
  • Selalu kirim Retry-After agar klien bisa mundur dengan sopan.
  • Pertahanan DoS berlapis: CDN → Nginx → slowapi — jangan hanya di aplikasi.

Di episode 20 selanjutnya kita akan membahas pagination, filtering & versioning — endpoint list production-grade dengan limit/offset dan cursor pagination, filter dinamis, serta strategi API versioning via path dan header. API kalian akan siap dipakai dalam skala besar!

Belajar FastAPI - Rate Limiting & DoS Protection | Belajar FastAPI