Oct 27, 20265 min read

How to Add Redis to a Django App

Caching Django model reads in Redis with Encore, Django's built-in Redis cache backend, django-redis or redis-py, with invalidation on write and a Redis that runs locally.

Django pages often repeat the same queries on every request, and Redis is the usual place to keep the results. That adds a Redis server for each developer, a managed Redis in production, a CACHES setting per environment and invalidation code that has to run on every write.

This guide caches a Django model read in Redis with Encore, Django's built-in Redis backend, django-redis and redis-py. Each version reads through the cache and drops the key when the row is renamed.

What you need to decide:

  • Whether you cache whole responses or individual objects.
  • How long entries live and how writes invalidate them.
  • Where Redis runs locally and in production.
  • Whether views are async.

Redis options for Django at a glance

CapabilityEncoreDjango RedisCachedjango-redisredis-py
Setupredis.Database("cache")CACHES settingCACHES settingClient in a module
Async APIAwaitable methodsaget, aset wrap sync callsSynchronousredis.asyncio
CommandsStrings, lists, sets, sorted setsCache API onlyCache API plus raw clientAll commands
Local Redisencore run starts itRun your ownRun your ownRun your own
ProductionElastiCache or Memorystore in your accountAny hosted RedisAny hosted RedisAny hosted Redis

Encore: Redis for your existing Django project

Encore

Encore combines Redis databases and Postgres declared in your Django code with the servers behind them, starting both locally under encore run and provisioning ElastiCache on AWS or Memorystore on GCP in your own account on deploy. It is an infrastructure SDK added to the project, so models, the ORM and Django's migrations stay as they are.

Encore declares infrastructure in Python code, runs it locally and provisions it in your AWS or GCP account
Infrastructure declared in Python, running locally under encore run and provisioned in your own AWS or GCP account on deploy.

The database, secret key and cache are declared in notes/resources.py:

from encore import secrets from encore.storage import postgres, redis db = postgres.Database("notes") django_secret_key = secrets.SharedSecret[str]("DjangoSecretKey") # Encore starts this under `encore run`; ElastiCache or Memorystore when deployed. cache = redis.Database("cache")

The detail view reads through the cache with a five minute expiry and falls back to the async ORM on a miss:

import json from django.http import HttpRequest, JsonResponse from django.views.decorators.http import require_GET from .models import Note from .resources import cache @require_GET async def note_detail(request: HttpRequest, note_id: int) -> JsonResponse: key = f"note:{note_id}" cached = await cache.string.get[str](key) if cached is not None: return JsonResponse({"source": "cache", "note": json.loads(cached)}) note = await Note.objects.filter(pk=note_id).values("id", "title").afirst() if note is None: return JsonResponse({"error": "not found"}, status=404) await cache.string.set(key, json.dumps(note), ex=300) return JsonResponse({"source": "db", "note": note})

The rename view updates the row and then deletes the cached copy, so the next read goes to Postgres:

from django.views.decorators.csrf import csrf_exempt from django.views.decorators.http import require_POST @csrf_exempt # Stateless JSON API example without cookie auth. @require_POST async def rename_note(request: HttpRequest, note_id: int) -> JsonResponse: title = json.loads(request.body)["title"] updated = await Note.objects.filter(pk=note_id).aupdate(title=title) # Drop the cached copy so the next read sees the new title. await cache.generic.delete(f"note:{note_id}") return JsonResponse({"updated": updated})

Migrations run through encore exec, and encore run starts Postgres and Redis locally. The second read comes from the cache, and a rename sends the next read back to the database:

~/notes
$ encore exec -- uv run python manage.py migrate$ encore run$ curl localhost:4000/notes/1/{"source": "db", "note": {"id": 1, "title": "first"}}$ curl localhost:4000/notes/1/{"source": "cache", "note": {"id": 1, "title": "first"}}

Each request's trace shows the Redis lookup and, on a miss, the SQL query. On deploy, Encore provisions ElastiCache on AWS or Memorystore on GCP, with Postgres as RDS or Cloud SQL, in your own account.

An agent tuning a slow page can add a cached read, then confirm in the local trace that the second request skips the query.

Good to know

Delete after the write commits; inside transaction.atomic(), use transaction.on_commit so a rolled back update does not clear a valid entry. Keep a TTL as a safety net for writes that bypass the view, such as the admin or a management command.

Try Encore →

Django's built-in RedisCache

Django has shipped a Redis cache backend since 4.0:

CACHES = { "default": { "BACKEND": "django.core.cache.backends.redis.RedisCache", "LOCATION": "redis://127.0.0.1:6379", } }

Views then use the standard cache API, including the async variants:

from django.core.cache import cache note = await cache.aget(f"note:{note_id}") if note is None: note = await Note.objects.filter(pk=note_id).values("id", "title").afirst() await cache.aset(f"note:{note_id}", note, timeout=300)

Good to know

The async methods currently wrap the synchronous client in a thread. The API covers get, set, delete and counters, not lists or sorted sets, and you run Redis yourself in each environment.

django-redis

django-redis is the older third-party backend, still common in existing projects:

CACHES = { "default": { "BACKEND": "django_redis.cache.RedisCache", "LOCATION": "redis://127.0.0.1:6379/1", "OPTIONS": {"CLIENT_CLASS": "django_redis.client.DefaultClient"}, } }

Good to know

It adds get_redis_connection() for raw commands, compression and custom serializers. Migrating to the built-in backend is usually a settings change, unless the code uses those extras.

redis-py directly

For data structures beyond a cache, use the client directly:

import os import redis.asyncio as aioredis r = aioredis.Redis.from_url(os.environ["REDIS_URL"], decode_responses=True) async def top_notes(limit: int = 10) -> list[str]: return await r.zrevrange("notes:views", 0, limit - 1)

Good to know

You manage REDIS_URL per environment and the connection pool lifetime under ASGI. Leaderboards and queues fit here better than in the cache API.

How to choose

  • Encore if you want Redis and Postgres running locally and as managed services in your own AWS or GCP account from the same code.
  • Django's RedisCache for standard caching in a project that already has Redis per environment.
  • django-redis if existing code relies on its raw client or compression.
  • redis-py for sorted sets, streams and other structures outside the cache API.

Python support in Encore is in beta, and you can sign up for access to try it with your Django project.

Frequently asked questions

How do I add Redis caching to a Django app?

Read through the cache in the view and delete the key when the row changes. With Encore, declare cache = redis.Database("cache"), read with await cache.string.get[str](key), store with await cache.string.set(key, value, ex=300), and call await cache.generic.delete(key) after an update. encore run starts Redis locally, and on deploy it becomes ElastiCache or Memorystore.

Does Django support Redis without a third-party package?

Yes. Since Django 4.0, django.core.cache.backends.redis.RedisCache is built in and uses redis-py. Set it in CACHES with a LOCATION URL and use the regular cache API.

How do I invalidate the Django cache when a model changes?

Delete the key in the code path that writes the row, after the update. Expiry alone means readers can see the old value until it runs out, so pair a TTL with explicit deletes.

What is the difference between django-redis and Django's RedisCache?

Django's built-in backend covers the standard cache API. django-redis adds features such as access to the raw client, compression and pluggable serializers, and it predates the built-in backend.

Deploy to your cloud on AWS and Google Cloud
without managing infrastructure

Encore lets developers and agents define application infrastructure directly in code, then automatically provisions it from local development to production in your AWS or GCP account.

~/orders — claude
Claude Code
Claude Code v2.1.180Opus 5.5~/orders