DEV Community

Yasser Shkeir
Yasser Shkeir

Posted on

Protect your Django API with Smart Ratelimiting (Async + Redis) 🛡️

Rate limiting is one of those things you don't think about until you really need it. Maybe a script kiddie is hammering your login endpoint, or a legitimate partner's cron job went rogue and is eating up all your database connections.

If you're using Django, you might have used django-ratelimit in the past. It's a classic, but frankly, it hasn't kept up with modern Django needs—especially when it comes to Async/Await support and High Availability.

I just released django-smart-ratelimit v1.0.1 to solve exactly these problems. Here is how you can use it to build a production-ready defense system for your API.

The Problem: When Redis Dies, Your App Shouldn't đź’€

Most rate limiters are simple wrappers around a cache (like Redis). If Redis slows down or times out, your rate limiter throws an exception, and your users get a 500 error.

We can do better.

This library implements a Circuit Breaker pattern. If the backend (Redis/MongoDB) starts failing, the rate limiter detects it and "fails open" (or closed, your choice). This means your site stays up even if your rate limiting infrastructure is having a bad day.

Installation

pip install django-smart-ratelimit[redis]

1. The Basic Decorator

It looks familiar if you've used Django before, but under the hood, it's fully typed and async-compatible.

from django.http import JsonResponse
from django_smart_ratelimit import ratelimit

@ratelimit(key='ip', rate='10/m', block=True)
def my_view(request):
    return JsonResponse({"message": "Hello World!"})
Enter fullscreen mode Exit fullscreen mode

If the user exceeds 10 requests per minute, they receive a 429 Too Many Requests.

2. Going Async ⚡

This is where the library shines. It uses coredis for true non-blocking Redis operations.

@ratelimit(key='ip', rate='50/s', block=True)
async def my_async_endpoint(request):
    # This won't block your event loop while checking limits!
    data = await fancy_async_logic()
    return JsonResponse(data)
Enter fullscreen mode Exit fullscreen mode

3. Advanced: Dynamic Limits by User Type

You usually want to give authenticated users higher limits than anonymous ones. You can stack decorators nicely for this:

@ratelimit(key='ip', rate='10/m', group='anon')
@ratelimit(key='user', rate='100/m', group='auth')
def api_view(request):
    # ...
Enter fullscreen mode Exit fullscreen mode

Or use a custom callable for the rate:

def get_rate(group, request):
    if request.user.is_staff:
        return '1000/h'
    return '100/h'

@ratelimit(key='user', rate=get_rate, block=True)
def expensive_view(request):
    ...
Enter fullscreen mode Exit fullscreen mode

4. Choice of Algorithms đź§ 

Not all endpoints are the same. We support three algorithms:

  1. Token Bucket: Great for allowing "Bursts" of traffic (e.g., 100 requests at once, then refill slowly).
  2. Sliding Window: Precise limiting (prevents the "double-limit at the top of the hour" loophole).
  3. Fixed Window: Simple and fast.

You can configure this globally or per-view.

Migration

If you are currently using django-ratelimit, migration is effortless. In v1.0.1, we added an alias so you don't even need to rename your decorators immediately.

# Old
from ratelimit.decorators import ratelimit

# New
from django_smart_ratelimit import ratelimit
Enter fullscreen mode Exit fullscreen mode

Links & Source

I'd love for you to try it out and break it!

Happy coding! 🚀

Top comments (0)