Skip to content

Caching

Caching is one of the easiest ways to make a Django app feel fast.

In this note, you will learn:

  • what caching is,
  • when to use it (and when not to),
  • cache backends supported by Django,
  • how to configure Redis as a cache backend,
  • and two common caching approaches in views.

We use Redis in examples, but the core ideas apply to other backends too.

Caching means storing the result of expensive work (database queries, external API calls, heavy computations) in a temporary storage called a cache.

When the same request comes again, Django can return cached data instead of recomputing everything.

In Django, caching can be done at multiple levels:

  • view-level caching,
  • template fragment caching,
  • low-level caching (manual cache.get() / cache.set()).

Caching is most helpful when data is expensive to compute but does not change very often.

Example: A report page that runs heavy queries and updates only once per day.

Caching may not be a good fit for real-time or frequently changing data because users might see stale content.

Choose a caching strategy based on:

  • data freshness requirements,
  • request frequency,
  • computation cost,
  • acceptable staleness window.

Caching uses RAM and CPU resources. It can also add complexity, so monitor and test your setup.

Always validate:

  • cache hit rate,
  • response time improvements,
  • stale-data side effects,
  • invalidation behavior.

Django supports several cache backends, including:

  • In-Memory Cache: This is the default cache backend that stores data in memory. It is fast but not suitable for production environments as it does not persist data across server restarts.
  • File-Based Cache: This backend stores cached data in files on the filesystem. It is slower than in-memory caching but can be useful for development or small applications.
  • Database Cache: This backend uses a database table to store cached data. It is more persistent than in-memory caching but can add overhead to database operations.
  • Memcached: This is a high-performance, distributed memory caching system that can be used in production environments to cache data across multiple servers.
  • Redis: Similar to Memcached, Redis is an in-memory data structure store that can be used for caching. It offers additional features like persistence and support for complex data types.

Let us first create a deliberately slow endpoint so the caching improvement is easy to observe.

from rest_framework.decorators import api_view
from rest_framework.response import Response
import requests
@api_view(['GET'])
def slow_api(request):
data = requests.get("https://httpbin.org/delay/3").json()
# Simulate a slow API call with a 3-second delay
return Response(data)

Redis is an in-memory data store and one of the most common production cache backends for Django.

To use Redis with Django caching, install django-redis and configure CACHES in settings.

Installing and Configuring Redis and Caching

Section titled “Installing and Configuring Redis and Caching”
  1. Add Redis service(s) to your docker-compose.yml:

    services:
    redis:
    image: redis
    container_name: redis
    restart: always
    ports:
    - 6379:6379
    volumes:
    - ./data/redis:/data
    command: ["redis-server", "--appendonly", "yes"] # Enable data persistence (optional)
    redisinsight:
    image: redislabs/redisinsight
    container_name: redisinsight
    ports:
    - 8001:8001
    volumes:
    - ./data/redisinsight:/db
  2. Start the Redis service using Docker Compose:

    Terminal window
    docker compose up -d redis redisinsight

    You may also use docker-compose if that is what your system supports.

  3. Install the django-redis package:

    Docs: https://github.com/jazzband/django-redis

    Terminal window
    uv add django-redis
  4. Configure your Django settings to use Redis as the cache backend:

    # settings.py
    import os
    CACHES = {
    "default": {
    "BACKEND": "django_redis.cache.RedisCache",
    "LOCATION": os.getenv("REDIS_URL", "redis://localhost:6379/3"),
    "TIMEOUT": 60 * 15, # Cache timeout in seconds (15 minutes) (optional)
    "OPTIONS": {
    "CLIENT_CLASS": "django_redis.client.DefaultClient",
    }
    }
    }
  5. Set up caching in your view:

    There are two common ways:

    • view-level caching with cache_page (simpler, recommended),
    • low-level caching for custom control.
    • Function-based views:

      from django.views.decorators.cache import cache_page
      from rest_framework.decorators import api_view
      from rest_framework.response import Response
      import requests
      @api_view(['GET'])
      @cache_page(60 * 15) # Cache for 15 minutes
      def slow_api(request):
      data = requests.get("https://httpbin.org/delay/3").json()
      return Response(data)
    • Class-based views:

      from django.views.decorators.cache import cache_page
      from django.utils.decorators import method_decorator
      from rest_framework.response import Response
      from rest_framework.views import APIView
      import requests
      class SlowAPIView(APIView):
      @method_decorator(cache_page(60 * 15)) # Cache for 15 minutes
      def get(self, request):
      data = requests.get("https://httpbin.org/delay/3").json()
      return Response(data)

    For user-specific endpoints (dashboard/profile data), avoid naive shared caching unless your cache key includes user context.

Use caching in places where repeated requests happen and data is not changing every second:

  • Public API endpoints that return the same response for many users.
  • Heavy list pages with filtering/sorting that hit the database frequently.
  • Dashboard cards/statistics recalculated on every refresh.
  • External API integrations with rate limits or slow responses.
  • Template fragments like sidebars, category lists, and popular-post widgets.
  • Expensive computed results such as recommendation sets or report summaries.

Avoid or carefully design caching for:

  • Real-time data (stock ticker, live match score, live chat).
  • Highly personalized or permission-sensitive responses unless cache keys are scoped correctly.
  • Critical flows where stale data could cause incorrect decisions (for example, payment balance checks).