DEV Community

Cover image for How to rate limit FastAPI in 2026
zlynv
zlynv

Posted on Originally published at zlynv.github.io

How to rate limit FastAPI in 2026

Rate limiting is the simplest defense against abuse. Here's how to add it
to a FastAPI app in 2026, step by step.

Install

pip install drogue[fastapi]
Enter fullscreen mode Exit fullscreen mode

Basic rate limiting

from fastapi import FastAPI
from drogue.adapters.fastapi import DrogueLimiter

app = FastAPI()
limiter = DrogueLimiter(app, default_limits=["100/minute"])

@app.get("/api/data")
@limiter.limit("10/minute")
async def get_data():
    return {"data": "value"}
Enter fullscreen mode Exit fullscreen mode

That's it. Every request gets a limit. Exceed it and you get 429 with
standard headers: X-RateLimit-Limit, X-RateLimit-Remaining, Retry-After.

Per-route limits

@app.get("/api/public")
@limiter.limit("100/minute")
async def public():
    return {"ok": True}

@app.post("/api/login")
@limiter.limit("5/minute")    # strict: brute force protection
async def login(credentials: dict):
    return {"token": "..."}

@app.post("/api/upload")
@limiter.limit("10/minute")
async def upload(file: UploadFile):
    return {"filename": file.filename}
Enter fullscreen mode Exit fullscreen mode

No request: Request needed. drogue extracts identity from the ASGI
context automatically.

Choosing an algorithm

from drogue.core.rules.rule import AlgorithmType

@app.get("/api/data")
@limiter.limit("30/minute", algorithm=AlgorithmType.FIXED_WINDOW)
async def get_data():
    return {"data": "value"}
Enter fullscreen mode Exit fullscreen mode
Algorithm Behavior Use when
TOKEN_BUCKET (default) Allows short bursts above average rate General API protection
FIXED_WINDOW Hard cap per window, no bursts Login, payment, strict quotas
SLIDING_WINDOW Smooth rate limiting, no boundary spikes Fair API usage
GCRA Telecom-grade smooth traffic High-throughput APIs
LEAKY_BUCKET Constant-rate processing Background jobs, queues

Token Bucket is the default because it's burst-friendly. For brute force
protection on login endpoints, use FIXED_WINDOW.

Custom identity (not just IP)

from drogue.core.identity import HeaderExtractor

api_key_extractor = HeaderExtractor("X-API-Key")

@app.get("/api/data")
@limiter.limit("100/minute", key_func=api_key_extractor)
async def get_data():
    return {"data": "value"}
Enter fullscreen mode Exit fullscreen mode

Available extractors:

  • RemoteAddressExtractor (default) - client IP with X-Forwarded-For anti-spoof
  • UserExtractor - user ID from request context
  • HeaderExtractor - custom header value
  • CompositeExtractor - combine multiple

Redis for multi-process

from drogue.core.storage.redis import RedisStorage

storage = RedisStorage(url="redis://localhost:6379")
limiter = DrogueLimiter(app, storage=storage, default_limits=["100/minute"])
Enter fullscreen mode Exit fullscreen mode

With Redis, rate limits are shared across all workers and processes.

Custom 429 response

from starlette.responses import JSONResponse

def my_error(request, exc, headers):
    return JSONResponse(
        {"error": "slow_down", "retry_after": exc.retry_after},
        status_code=429,
        headers=headers,
    )

limiter = DrogueLimiter(app, error_renderer=my_error)
Enter fullscreen mode Exit fullscreen mode

Rate limit headers are injected automatically.

WebSocket rate limiting

@app.websocket("/ws/chat")
@limiter.limit_ws("10/minute")
async def chat(websocket: WebSocket):
    await websocket.accept()
    ...
Enter fullscreen mode Exit fullscreen mode

What's next

  • DDoS detection - Z-score anomaly detection
  • Auto-ban - progressive bans for repeat offenders
  • Trust scoring - 9x throughput for verified users
  • CAPTCHA challenges - Turnstile integration for page traffic

All off by default. Enable what you need.

pip install drogue[fastapi]
Enter fullscreen mode Exit fullscreen mode

Full docs |
GitHub

Top comments (0)