Module 5: Capstone Project — API with Caching Strategy

Final Project: Production Cached API

Overview

This is the capstone module of the Redis & Caching Strategies guide. Across 4 modules you learned Redis's 5 data types, the 3 caching patterns, sliding window rate limiting with sorted sets, sessions complementing JWT, Pub/Sub for invalidation events, and professional integration with FastAPI using redis.asyncio. Each module delivered something concrete. Now you combine them into a single portfolio-worthy project: an e-commerce API with every production feature a professional backend developer should be able to build.

The Production Cached API isn't a step-by-step tutorial. It's clear specs and reference code. You implement each piece, integrate them into a coherent architecture, measure performance with real benchmarks, and ship it with a professional README + Docker Compose + tests. The difference between "knowing Redis" and "having a project that demonstrates command of it" is exactly this module.

If you're coming from module 4 with all the patterns fresh, this module is the natural close: the project that combined HTTP POST → Pub/Sub → WebSocket in module 4's capsule 05 was one piece; now you assemble ALL the pieces into a real production app. By the end of the module, you'll have a project on GitHub any recruiter can review — with passing tests, measurable metrics, working graceful degradation, and professional deployment with Docker Compose.


What is the Production Cached API?

It's a simulated e-commerce API with every Redis feature you've learned:

┌─────────────────────────────────────────────────────────┐
│         PRODUCTION CACHED API                            │
│                                                          │
│  Endpoints:                                              │
│  ├── GET  /api/products           [cache-aside]         │
│  ├── GET  /api/products/{id}      [cache-aside]         │
│  ├── POST /api/products           [admin, invalidates]  │
│  ├── GET  /api/categories         [cache-aside, long]   │
│  ├── GET  /api/users/{id}         [cache-aside, hash]   │
│  ├── PUT  /api/users/{id}         [write-through]       │
│  ├── POST /analytics/event        [write-behind]        │
│  ├── POST /auth/login             [JWT + session]       │
│  ├── POST /auth/logout            [session revoke]      │
│  ├── GET  /me                     [JWT + session check] │
│  └── WS   /ws/notifications       [Pub/Sub bridge]      │
│                                                          │
│  Cross-cutting:                                          │
│  ├── Multi-tier rate limiting (free/pro/enterprise)     │
│  ├── Cache invalidation events via Pub/Sub              │
│  ├── Metrics: hit rate, latency, error rate             │
│  └── A health check that verifies the dependencies       │
│                                                          │
│  Stack:                                                  │
│  ├── FastAPI (async)                                     │
│  ├── PostgreSQL (mocked with SQLite or an in-memory dict)│
│  ├── Redis 7 (cache + sessions + Pub/Sub + rate limit)   │
│  └── Docker Compose                                      │
└─────────────────────────────────────────────────────────┘

The architecture

                        ┌─────────────┐
                        │   Client    │
                        └──────┬──────┘
                               │
                               │ HTTP / WebSocket
                               ▼
       ┌─────────────────────────────────────────────────┐
       │              The FastAPI App                     │
       │                                                   │
       │  ┌────────────────────────────────────────────┐  │
       │  │  The middleware stack (in order)           │  │
       │  │  1. CORS                                    │  │
       │  │  2. Rate Limit (sliding window)             │  │
       │  │  3. Cache (auto-caches GET endpoints)       │  │
       │  │  4. Logging                                 │  │
       │  └────────────────────────────────────────────┘  │
       │                      │                            │
       │                      ▼                            │
       │  ┌────────────────────────────────────────────┐  │
       │  │  Routers                                    │  │
       │  │  ├─ /api/products    (cache-aside)         │  │
       │  │  ├─ /api/users       (cache-aside + WT)    │  │
       │  │  ├─ /analytics       (write-behind)        │  │
       │  │  ├─ /auth            (JWT + sessions)      │  │
       │  │  └─ /ws              (a WebSocket bridge)  │  │
       │  └────────────────────────────────────────────┘  │
       │                      │                            │
       └──────────────────────┼────────────────────────────┘
                              │
              ┌───────────────┼───────────────┐
              ▼               ▼               ▼
       ┌──────────┐    ┌─────────────┐   ┌────────────┐
       │PostgreSQL│    │    Redis    │   │  Worker    │
       │          │    │             │   │  (write-   │
       │  Source  │◄───│ - Cache     │   │   behind   │
       │  of      │    │ - Sessions  │   │   flush)   │
       │  truth   │    │ - Rate lim  │   │            │
       │          │    │ - Pub/Sub   │   │            │
       └──────────┘    └─────────────┘   └────────────┘

The flow of a GET /api/products

1. The request arrives at FastAPI
2. The CORS middleware (passes)
3. The rate limit middleware:
   - Identifies the user_id from the JWT
   - Checks the sliding window per user + endpoint category
   - If it exceeds → 429 Too Many Requests
   - If OK → continue, adding the X-RateLimit-* headers
4. The cache middleware:
   - Generates a cache key from the path + query params
   - Try GET cache:http:/api/products:hash
   - If HIT → return the cached value (with an X-Cache: HIT header)
   - If MISS → run the endpoint
5. The endpoint:
   - Queries PostgreSQL (with optional internal cache-aside)
   - Returns the data
6. The cache middleware stores the response (a 5 min TTL)
7. The logging middleware: logs the request
8. The client receives the response with all the headers

The flow of a POST /api/products (admin)

1. The request arrives
2. Rate limit (the admin has the "enterprise" tier)
3. The cache middleware skips it (POSTs aren't cached)
4. The endpoint:
   - INSERT into PostgreSQL
   - DELETE the local cache: cache:product:{id}, cache:products:list
   - PUBLISH the event "cache:invalidate:product:{id}" on Redis Pub/Sub
5. Other services subscribed to the pattern receive the event and invalidate THEIR caches
6. Return the created product

The complete endpoints

Authentication

MethodEndpointDescription
POST/auth/loginCreate a session + JWT
POST/auth/logoutRevoke the current session
POST/auth/logout-allRevoke all the user's sessions
GET/auth/sessionsA list of active devices
POST/auth/sessions/{sid}/revokeRevoke a specific device

The protected API

MethodEndpointPatternRate limit category
GET/api/productsCache-aside, TTL 5mingeneral
GET/api/products/{id}Cache-aside, TTL 10mingeneral
POST/api/productsInvalidation + Pub/Subadmin
GET/api/categoriesCache-aside, TTL 1hgeneral
GET/api/users/{id}Cache-aside with a hash, TTL 30mingeneral
PUT/api/users/{id}Write-throughgeneral
GET/api/searchCache the top queries, TTL 2minsearch
POST/api/ordersNo cache (transactional)orders
POST/analytics/eventWrite-behindanalytics

System

MethodEndpointDescription
GET/healthStatus + dependencies
GET/metricsCache hit rate, request counts, etc.
WS/ws/notificationsA stream of real-time events

Admin

MethodEndpointDescription
POST/admin/users/{id}/revokeForce a global logout
POST/admin/cache/invalidate-allMass invalidation (a version bump)
GET/admin/metrics/fullDetailed metrics

Rate limits per tier

TierGeneralSearchOrdersAnalytics
free100/hr20/hr10/hr1000/hr
pro1000/hr200/hr100/hr10000/hr
enterprise10000/hr2000/hr1000/hr100000/hr

Standard HTTP headers on every response: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, Retry-After (on a 429).


The caching strategy per endpoint

EndpointPatternTTLInvalidationJustification
GET /api/productsCache-aside5minDELETE on POSTMassive reads, infrequent changes
GET /api/products/{id}Cache-aside10minDELETE + Pub/SubSame reasoning, a specific key
GET /api/categoriesCache-aside1hManualRare changes
GET /api/users/{id}Cache-aside with a hash30min slidingDELETE on PUTA sliding TTL for active users
PUT /api/users/{id}Write-throughThe UX requires immediate consistency
GET /api/searchCache-aside (top queries)2minThe natural TTLAn infinite space, only cache the common ones
POST /api/ordersNO cache (transactional)A critical transaction
POST /analytics/eventWrite-behindMassive volume, losing some is fine

Metrics to track

Cache:
- hits_total (a counter per endpoint)
- misses_total (a counter)
- hit_rate (calculated)
- latency_with_cache (a histogram, p50/p95/p99)
- latency_without_cache (a histogram)

Rate limiting:
- requests_allowed_total
- requests_rate_limited_total (429s)
- rate_limit_by_tier

Sessions:
- active_sessions_total
- sessions_created_total
- sessions_revoked_total
- logout_all_events_total

Pub/Sub:
- events_published_total
- events_received_total

System:
- redis_connected (boolean)
- redis_response_time_ms
- ws_clients_connected

The evaluation rubric (100 points)

CategoryPointsCriterion
Caching25The 3 patterns implemented with judgment (cache-aside, write-through, write-behind), a differentiated TTL per endpoint, correct invalidation (manual + event-driven)
Rate Limiting20A sliding window with sorted sets, multi-tier, standard HTTP headers, a correct 429
Sessions + Auth15JWT + Redis sessions, local + global logout, list devices, a sliding TTL
Pub/Sub10Cache invalidation events, a working WebSocket bridge
Async Integration10redis.asyncio, connection pooling, dependency injection, lifespan events
Graceful Degradation10The app works without Redis (degraded), structured logs, no crash
Docker Compose5The complete stack (API + Redis + DB), docker-compose up works
Tests10Integration tests with pytest-asyncio, at least 10 tests passing
Documentation5A professional README, a caching strategy document, a complete OpenAPI
Total100

The levels:

  • 90-100: Excellent — ready for a portfolio
  • 75-89: Good — functional with minor details
  • 60-74: Acceptable — it works but needs polish
  • <60: Needs work — review the previous modules

Technologies

ToolVersionUse
Python3.10+The runtime
FastAPI0.109+The framework
Redis7+Cache + Sessions + Pub/Sub + Rate limit
redis-py>= 5.0The official async client (NOT aioredis)
PyJWT2.8+JWT signing
Pydanticv2Validation
uvicornlatestThe ASGI server
Docker Compose2.xLocal orchestration
pytest + httpxlatestTests

requirements.txt

fastapi>=0.136
uvicorn[standard]>=0.27.0
redis>=7.4
pyjwt>=2.8.0
pydantic>=2.0
httpx>=0.26.0
pytest>=8.0.0
pytest-asyncio>=0.23.0
websockets>=12.0
python-multipart>=0.0.9

⚠️ Do NOT install aioredis — use redis.asyncio (included in redis-py >= 4.2).


The project's structure

production-cached-api/
├── .venv/
├── app/
│   ├── __init__.py
│   ├── main.py                    # The FastAPI app + lifespan + middleware
│   ├── config.py                   # Configuration + tier limits
│   ├── redis_client.py             # The pool's singleton
│   ├── models.py                   # Pydantic models
│   ├── auth/
│   │   ├── __init__.py
│   │   ├── jwt_handler.py
│   │   └── sessions.py
│   ├── caching/
│   │   ├── __init__.py
│   │   ├── cache_aside.py
│   │   ├── write_through.py
│   │   └── write_behind.py
│   ├── rate_limit/
│   │   ├── __init__.py
│   │   ├── sliding_window.py
│   │   └── middleware.py
│   ├── pubsub/
│   │   ├── __init__.py
│   │   ├── publisher.py
│   │   └── listener.py
│   ├── routers/
│   │   ├── __init__.py
│   │   ├── auth.py
│   │   ├── api.py
│   │   ├── analytics.py
│   │   ├── admin.py
│   │   └── system.py
│   └── metrics/
│       ├── __init__.py
│       └── tracker.py
├── tests/
│   ├── __init__.py
│   ├── test_auth.py
│   ├── test_rate_limit.py
│   ├── test_caching.py
│   ├── test_pubsub.py
│   └── test_integration.py
├── scripts/
│   ├── seed_data.py
│   ├── benchmark.py
│   └── verify.sh
├── docker-compose.yml
├── Dockerfile
├── .env.example
├── requirements.txt
├── README.md
└── CACHING-STRATEGY.md             # The strategy document (a deliverable!)

How the modules connect

An HTTP request ──────────────────────────────────────►
    │                                                   │
    ├── Module 4: Async + middleware ──────────────────►│
    │                                                   │
    ├── Module 3: Rate limit (sliding window) ─────────►│
    │                                                   │
    ├── Module 2: The cache check (cache-aside) ───────►│
    │                                                   │
    ├── Module 1: Strings/Hashes/Sorted Sets ──────────►│
    │   (the fundamental data types everything uses)   │
    │                                                   │
    ├── Module 4: A Pub/Sub broadcast on writes ───────►│
    │                                                   │
    └── Module 4: WebSocket delivery to clients ────────►│

Each module contributes a piece. M5 assembles them.


How to use the next capsules

Capsules 02-04 guide you through the implementation in 3 phases:

  1. Capsule 02: Caching strategy design + the base architecture — an endpoint audit, the decisions, the folder structure, the models, redis_client
  2. Capsule 03: Implementation: caching + rate limiting — endpoints with cache-aside, the rate limit middleware, HTTP headers
  3. Capsule 04: Implementation: sessions + Pub/Sub + monitoring — JWT auth, the session store, invalidation events, metrics

Capsule 05 has the close: end-to-end verification, Docker Compose, a README, tests, portfolio guidance.

A recommendation: Implement each phase without copying the code first. If you get stuck for more than 30 minutes, open the corresponding capsule from the relevant module (M2 for caching, M3 for rate limiting, M4 for sessions/pubsub) and refresh the pattern. M5's capsules are a reference, not an instruction manual.


Prerequisites

  • Modules 1-4 completed (with every concept fresh)
  • Redis running in Docker
  • Python 3.10+ with a virtual environment
  • redis-py >= 5.0 installed
  • Docker Compose available (for the final deployment)

The initial setup

mkdir -p ~/projects/redis-guide/module-05-final/production-cached-api
cd ~/projects/redis-guide/module-05-final/production-cached-api

python -m venv .venv
source .venv/bin/activate
pip install fastapi "uvicorn[standard]" "redis>=7.4" pyjwt pydantic httpx websockets pytest pytest-asyncio

# The structure
mkdir -p app/auth app/caching app/rate_limit app/pubsub app/routers app/metrics tests scripts static
touch app/__init__.py app/auth/__init__.py app/caching/__init__.py
touch app/rate_limit/__init__.py app/pubsub/__init__.py app/routers/__init__.py app/metrics/__init__.py
touch tests/__init__.py

Resources

  1. FastAPI Tutorial — A quick reference
  2. Redis Best Practices for Python — The official patterns
  3. 12-Factor App — Principles for production-ready apps
  4. Awesome FastAPI — Additional resources
  5. Docker Compose for Python apps — The official setup

What's next?

In Capsule 02 you get into the implementation: defining the concrete caching strategy for each endpoint, creating the folder structure, configuring the redis_client.py singleton, and setting up the FastAPI scaffolding with a lifespan + middleware stack. It's the foundation on top of which M5's capsules 03 and 04 build the complete features.

If modules 1-4 are fresh and your workspace is ready, let's go.