Django cookbook · Beginner
Rate limit API endpoints in DRF
Login and expensive endpoints get hammered by scripts.
Approach
Configure default user and anonymous rates, and use scoped throttles for sensitive endpoints. Use a shared cache (Redis) so limits apply across all application servers.
Solution
# settings.py
REST_FRAMEWORK = {
"DEFAULT_THROTTLE_CLASSES": [
"rest_framework.throttling.AnonRateThrottle",
"rest_framework.throttling.UserRateThrottle",
"rest_framework.throttling.ScopedRateThrottle",
],
"DEFAULT_THROTTLE_RATES": {"anon": "60/min", "user": "600/min", "login": "5/min", "exports": "10/hour"},
}
CACHES = {"default": {"BACKEND": "django.core.cache.backends.redis.RedisCache", "LOCATION": "redis://redis:6379/1"}}
# views.py
from rest_framework.views import APIView
class LoginView(APIView):
throttle_scope = "login"
authentication_classes = []
permission_classes = []
def post(self, request):
...
class ExportView(APIView):
throttle_scope = "exports"
def get(self, request):
...
Pitfalls
- Using the default local-memory cache with several processes; each has its own counters.
- Behind a proxy, every client appears as the proxy IP; configure
NUM_PROXIEScorrectly. - Throttling as the only protection for login; also lock out or slow down per account.
Variations
- Subclass
SimpleRateThrottleto throttle per API key or per tenant. - Apply coarse limits at the reverse proxy or gateway too.
Further reading: DRF throttling
Reference: https://www.django-rest-framework.org/api-guide/throttling/
More Django recipes
- Add a required column without downtime
- Add full-text search with PostgreSQL
- Add health checks and structured logging
- Avoid N+1 queries with select_related and prefetch_related
- Cache expensive queries and invalidate on change
- Call external APIs concurrently in async views
- Configure settings from environment variables
- Enforce rules with database constraints