Django Rate Limiting — Protecting Views with django-ratelimit
In this tutorial, you will learn about Django Rate Limiting. We cover key concepts, practical examples, and best practices to help you master this topic.
Django-ratelimit provides decorators and mixins for rate limiting views in Django applications, supporting IP-based, user-based, and custom key functions with multiple blocking strategies.
What You'll Learn
- How to install and configure django-ratelimit
- How to apply rate limits to function-based and class-based views
- How to use custom key functions for user-based limiting
Why It Matters
Django powers many content sites and APIs. Without rate limiting, a single user can scrape your entire database or brute-force login forms. Django-ratelimit integrates natively with Django's view system, requiring minimal code changes.
Real-World Use
DodaTech's Django-based API uses django-ratelimit on three views: the login view (5 req/min per IP+username), the search API (30 req/min per user), and the contact form (2 req/min per IP). Rate-limited users receive a 429 response with a Retry-After header.
flowchart LR
A["Request"] --> B["Django View"]
B --> C{"Rate Limit\nDecorator"}
C -->|"Within limit"| D["Process\nRequest"]
C -->|"Exceeded"| E["429 Response"]
D --> F["Template or\nJSON Response"]
style B fill:#dbeafe,stroke:#2563eb
style E fill:#fecaca,stroke:#dc2626
Basic Usage
# views.py
from django_ratelimit.decorators import ratelimit
from django.http import JsonResponse
@ratelimit(key='ip', rate='10/m', method='GET')
def api_data(request):
was_limited = getattr(request, 'limited', False)
if was_limited:
return JsonResponse(
{'error': 'rate_limit_exceeded'},
status=429
)
return JsonResponse({'data': 'response data'})
User-Based Rate Limiting
from django_ratelimit.decorators import ratelimit
from django.contrib.auth.decorators import login_required
@ratelimit(key='user', rate='100/h', method='ALL')
@login_required
def user_dashboard(request):
if getattr(request, 'limited', False):
return JsonResponse({
'error': 'rate_limit_exceeded',
'message': 'You have exceeded your hourly limit.',
'retry_after': 3600
}, status=429)
return render(request, 'dashboard.html', {
'user': request.user
})
Custom Key Functions
# Custom key function - rate limit by IP + username combination
def auth_rate_key(group, request):
ip = request.META.get('REMOTE_ADDR')
username = request.POST.get('username', '')
return f'{ip}:{username}'
@ratelimit(key=auth_rate_key, rate='5/m', method='POST')
def login_view(request):
if getattr(request, 'limited', False):
return JsonResponse({
'error': 'too_many_attempts',
'message': 'Too many login attempts. Try again in 1 minute.',
'retry_after': 60
}, status=429)
# Process login
username = request.POST.get('username')
password = request.POST.get('password')
user = authenticate(request, username=username, password=password)
if user is not None:
login(request, user)
return JsonResponse({'status': 'logged_in'})
return JsonResponse({'error': 'invalid_credentials'}, status=401)
Class-Based Views
from django.utils.decorators import method_decorator
from django.views import View
@method_decorator(ratelimit(key='ip', rate='30/m', method='GET'), name='dispatch')
class SearchView(View):
def get(self, request):
if getattr(request, 'limited', False):
return JsonResponse({
'error': 'rate_limit_exceeded',
'message': 'Search rate limit exceeded.'
}, status=429)
query = request.GET.get('q', '')
results = perform_search(query)
return JsonResponse({'results': results, 'count': len(results)})
Common Mistakes
1. Not Checking request.limited
The decorator sets request.limited = True but does not automatically block the request. You must check this attribute and return a 429 response.
2. Using IP-Based Limiting Behind a Proxy
Behind a reverse proxy, REMOTE_ADDR is the proxy IP. Use request.META.get('HTTP_X_FORWARDED_FOR') instead and configure the proxy headers.
3. Not Specifying method
Without method='POST', the decorator limits all methods including GET for the login page, not just the POST submission.
4. Too Low Limits on Search Endpoints
Search endpoints need higher limits than auth endpoints. 30-60 req/min is reasonable for read APIs.
5. Not Using the Block Option
Set block=True to have django-ratelimit automatically return 429 without needing to check request.limited.
Practice Questions
- What decorator does django-ratelimit provide?
- How do you rate limit by authenticated user?
- What does setting
block=Truedo? - Why might you need a custom key function?
- How do you handle rate limiting behind a proxy?
Answers
@ratelimit(key='...', rate='...', method='...'). 2. Usekey='user'orkey=user_or_ip. 3. Automatically returns 429 without checking request.limited. 4. For rate limiting by IP+username combination for auth endpoints. 5. Use HTTP_X_FORWARDED_FOR header instead of REMOTE_ADDR.
Challenge
Build a Django application with: a login view limited to 5 POST attempts per IP+username per minute, a search API limited to 30 req/min per authenticated user, an admin view with 100 req/min limit, and a custom rate limit middleware that adds X-RateLimit headers to all responses.
FAQ
Mini Project
Create a Django application with django-ratelimit that implements: per-IP public API limits (30 req/min), per-user authenticated API limits (100 req/min), per-IP+username auth endpoint limits (5 req/min), custom 429 error pages with Retry-After headers, and a Django management command to view current rate limit data from the cache.
What's Next
- Learn about Spring Boot rate limiting with Bucket4j
- Explore FastAPI rate limiting with SlowAPI
- Continue to distributed rate limiting with Redis cluster
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro