Django REST Framework (DRF) throttling is how you prevent your API from being overwhelmed by too many requests, either accidentally or maliciously.
Let’s see it in action. Imagine a simple UserViewSet that we want to rate limit.
# api/views.py
from rest_framework import viewsets, permissions
from django.contrib.auth.models import User
from .serializers import UserSerializer
from rest_framework.throttling import UserRateThrottle
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = [permissions.IsAuthenticated]
throttle_classes = [UserRateThrottle]
throttle_scope = 'user_list'
Here, UserRateThrottle is a built-in DRF class. We’ve applied it to our UserViewSet. We also defined a throttle_scope named 'user_list'. This scope is crucial because it’s how we configure the actual limits.
Now, in your settings.py, you’ll define these scopes:
# settings.py
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': [
'rest_framework.throttling.AnonRateThrottle',
'rest_framework.throttling.UserRateThrottle',
'rest_framework.throttling.ScopedRateThrottle',
],
'DEFAULT_THROTTLE_RATES': {
'anon': '1000/day',
'user': '1000/day',
'user_list': '5/minute', # Our custom scope
}
}
DEFAULT_THROTTLE_CLASSES tells DRF which throttling classes to consider by default. DEFAULT_THROTTLE_RATES is a dictionary where keys are the scopes (or anon/user for the default scopes) and values are the rate limits. '5/minute' means a user (or anonymous IP, depending on the throttle class) can make at most 5 requests to endpoints using the user_list scope within any given minute.
When a request comes in, DRF checks the throttle_classes on the view. For our UserViewSet, it sees UserRateThrottle. UserRateThrottle identifies the request by the authenticated user’s ID. Then, it looks at the throttle_scope 'user_list' defined on the view. It checks the DEFAULT_THROTTLE_RATES in settings.py for the 'user_list' scope and finds '5/minute'. DRF maintains a cache (usually Redis or Memcached) to track request counts per user/IP per scope. If the count for that user/scope exceeds 5 within the last minute, DRF returns a 429 Too Many Requests response.
The most surprising true thing about DRF throttling is that ScopedRateThrottle is the most flexible and often the most misunderstood. While AnonRateThrottle and UserRateThrottle are convenient, ScopedRateThrottle lets you define different rate limits for different views or viewsets, all within the same DEFAULT_THROTTLE_RATES dictionary, by simply assigning a unique scope name to each. This allows for granular control without needing custom throttle classes for every specific limit.
This system works by associating a unique key with each request that needs throttling. For UserRateThrottle, this key is typically the user’s primary key. For AnonRateThrottle, it’s the client’s IP address. The ScopedRateThrottle then combines this identifier with the throttle_scope to create a distinct cache key. For example, if user ID 123 requests an endpoint with scope 'user_list', the cache key might look something like throttle-user-list-123. DRF increments a counter for this key in the cache and checks if it exceeds the configured rate. If it does, a 429 response is issued with Retry-After headers indicating when the client can try again.
Beyond basic rate limiting, you can implement custom throttling logic by subclassing BaseThrottle and overriding get_cache_key and allow_request. This allows for complex scenarios, like throttling based on request body content, specific user groups, or even external service responses.
The next concept you’ll likely explore is how to handle different throttling strategies for different parts of your API, perhaps using ScopedRateThrottle more extensively or combining it with custom throttle_classes.