[{"data":1,"prerenderedAt":1120},["ShallowReactive",2],{"nav":3,"page-\u002Fasync-background-tasks-observability\u002Fcaching-strategies\u002Fredis-response-caching-in-fastapi\u002F":310,"surround-\u002Fasync-background-tasks-observability\u002Fcaching-strategies\u002Fredis-response-caching-in-fastapi\u002F":1118},[4,96,212],{"title":5,"path":6,"stem":7,"children":8},"Advanced Pydantic Validation Serialization","\u002Fadvanced-pydantic-validation-serialization","advanced-pydantic-validation-serialization",[9,12,30,42,54,66,90],{"title":10,"path":6,"stem":11},"Advanced Pydantic Validation and Serialization","advanced-pydantic-validation-serialization\u002Findex",{"title":13,"path":14,"stem":15,"children":16},"Custom Validators and Field Constraints in Pydantic","\u002Fadvanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints","advanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Findex",[17,18,24],{"title":13,"path":14,"stem":15},{"title":19,"path":20,"stem":21,"children":22},"Creating Reusable Custom Validators in Pydantic","\u002Fadvanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fcreating-reusable-custom-validators-in-pydantic","advanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fcreating-reusable-custom-validators-in-pydantic\u002Findex",[23],{"title":19,"path":20,"stem":21},{"title":25,"path":26,"stem":27,"children":28},"Pydantic v2 Async Custom Validator: What to Do Instead","\u002Fadvanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fpydantic-v2-async-custom-validator","advanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fpydantic-v2-async-custom-validator\u002Findex",[29],{"title":25,"path":26,"stem":27},{"title":31,"path":32,"stem":33,"children":34},"JSON Schema Customization in Pydantic and FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fjson-schema-customization","advanced-pydantic-validation-serialization\u002Fjson-schema-customization\u002Findex",[35,36],{"title":31,"path":32,"stem":33},{"title":37,"path":38,"stem":39,"children":40},"Customizing OpenAPI Schema Generation in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fjson-schema-customization\u002Fcustomizing-openapi-schema-generation-in-fastapi","advanced-pydantic-validation-serialization\u002Fjson-schema-customization\u002Fcustomizing-openapi-schema-generation-in-fastapi\u002Findex",[41],{"title":37,"path":38,"stem":39},{"title":43,"path":44,"stem":45,"children":46},"Nested Model Serialization in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fnested-model-serialization","advanced-pydantic-validation-serialization\u002Fnested-model-serialization\u002Findex",[47,48],{"title":43,"path":44,"stem":45},{"title":49,"path":50,"stem":51,"children":52},"Handling Deeply Nested JSON Models Efficiently","\u002Fadvanced-pydantic-validation-serialization\u002Fnested-model-serialization\u002Fhandling-deeply-nested-json-models-efficiently","advanced-pydantic-validation-serialization\u002Fnested-model-serialization\u002Fhandling-deeply-nested-json-models-efficiently\u002Findex",[53],{"title":49,"path":50,"stem":51},{"title":55,"path":56,"stem":57,"children":58},"Performance Optimization for Pydantic Models in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models","advanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models\u002Findex",[59,60],{"title":55,"path":56,"stem":57},{"title":61,"path":62,"stem":63,"children":64},"Pydantic Model Serialization Performance in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models\u002Fpydantic-model-serialization-performance","advanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models\u002Fpydantic-model-serialization-performance\u002Findex",[65],{"title":61,"path":62,"stem":63},{"title":67,"path":68,"stem":69,"children":70},"Pydantic V2 Migration Guide for FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide","advanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Findex",[71,72,78,84],{"title":67,"path":68,"stem":69},{"title":73,"path":74,"stem":75,"children":76},"Migrate @validator to @field_validator in Pydantic v2","\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Fmigrate-validator-to-field-validator","advanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Fmigrate-validator-to-field-validator\u002Findex",[77],{"title":73,"path":74,"stem":75},{"title":79,"path":80,"stem":81,"children":82},"Migrating from Pydantic v1 to v2 Without Breaking APIs","\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Fmigrating-from-pydantic-v1-to-v2-without-breaking-apis","advanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Fmigrating-from-pydantic-v1-to-v2-without-breaking-apis\u002Findex",[83],{"title":79,"path":80,"stem":81},{"title":85,"path":86,"stem":87,"children":88},"model_config vs class Config in Pydantic v2","\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Fmodel-config-vs-class-config","advanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Fmodel-config-vs-class-config\u002Findex",[89],{"title":85,"path":86,"stem":87},{"title":91,"path":92,"stem":93,"children":94},"Type Hinting and IDE Integration in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Ftype-hinting-ide-integration","advanced-pydantic-validation-serialization\u002Ftype-hinting-ide-integration\u002Findex",[95],{"title":91,"path":92,"stem":93},{"title":97,"path":98,"stem":99,"children":100},"Async Background Tasks Observability","\u002Fasync-background-tasks-observability","async-background-tasks-observability",[101,104,122,140,158,176,194],{"title":102,"path":98,"stem":103},"Async, Background Tasks, and Observability in FastAPI","async-background-tasks-observability\u002Findex",{"title":105,"path":106,"stem":107,"children":108},"Async Correctness and Concurrency in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-correctness-concurrency","async-background-tasks-observability\u002Fasync-correctness-concurrency\u002Findex",[109,110,116],{"title":105,"path":106,"stem":107},{"title":111,"path":112,"stem":113,"children":114},"FastAPI async def vs def: Performance and When to Use Each","\u002Fasync-background-tasks-observability\u002Fasync-correctness-concurrency\u002Ffastapi-async-def-vs-def-performance","async-background-tasks-observability\u002Fasync-correctness-concurrency\u002Ffastapi-async-def-vs-def-performance\u002Findex",[115],{"title":111,"path":112,"stem":113},{"title":117,"path":118,"stem":119,"children":120},"Fixing Blocking Calls in Async FastAPI Routes","\u002Fasync-background-tasks-observability\u002Fasync-correctness-concurrency\u002Ffixing-blocking-calls-in-async-routes","async-background-tasks-observability\u002Fasync-correctness-concurrency\u002Ffixing-blocking-calls-in-async-routes\u002Findex",[121],{"title":117,"path":118,"stem":119},{"title":123,"path":124,"stem":125,"children":126},"Async Database Sessions in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-database-sessions","async-background-tasks-observability\u002Fasync-database-sessions\u002Findex",[127,128,134],{"title":123,"path":124,"stem":125},{"title":129,"path":130,"stem":131,"children":132},"Async SQLAlchemy Session per Request in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-database-sessions\u002Fasync-sqlalchemy-session-per-request","async-background-tasks-observability\u002Fasync-database-sessions\u002Fasync-sqlalchemy-session-per-request\u002Findex",[133],{"title":129,"path":130,"stem":131},{"title":135,"path":136,"stem":137,"children":138},"Fixing asyncpg Connection Pool Exhaustion in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-database-sessions\u002Ffixing-asyncpg-pool-exhaustion","async-background-tasks-observability\u002Fasync-database-sessions\u002Ffixing-asyncpg-pool-exhaustion\u002Findex",[139],{"title":135,"path":136,"stem":137},{"title":141,"path":142,"stem":143,"children":144},"Background Task Processing in FastAPI","\u002Fasync-background-tasks-observability\u002Fbackground-task-processing","async-background-tasks-observability\u002Fbackground-task-processing\u002Findex",[145,146,152],{"title":141,"path":142,"stem":143},{"title":147,"path":148,"stem":149,"children":150},"FastAPI BackgroundTasks vs Celery vs ARQ","\u002Fasync-background-tasks-observability\u002Fbackground-task-processing\u002Ffastapi-backgroundtasks-vs-celery-vs-arq","async-background-tasks-observability\u002Fbackground-task-processing\u002Ffastapi-backgroundtasks-vs-celery-vs-arq\u002Findex",[151],{"title":147,"path":148,"stem":149},{"title":153,"path":154,"stem":155,"children":156},"Running ARQ Workers with FastAPI","\u002Fasync-background-tasks-observability\u002Fbackground-task-processing\u002Frunning-arq-workers-with-fastapi","async-background-tasks-observability\u002Fbackground-task-processing\u002Frunning-arq-workers-with-fastapi\u002Findex",[157],{"title":153,"path":154,"stem":155},{"title":159,"path":160,"stem":161,"children":162},"Caching Strategies in FastAPI","\u002Fasync-background-tasks-observability\u002Fcaching-strategies","async-background-tasks-observability\u002Fcaching-strategies\u002Findex",[163,164,170],{"title":159,"path":160,"stem":161},{"title":165,"path":166,"stem":167,"children":168},"Cache Invalidation Patterns in FastAPI","\u002Fasync-background-tasks-observability\u002Fcaching-strategies\u002Fcache-invalidation-patterns-in-fastapi","async-background-tasks-observability\u002Fcaching-strategies\u002Fcache-invalidation-patterns-in-fastapi\u002Findex",[169],{"title":165,"path":166,"stem":167},{"title":171,"path":172,"stem":173,"children":174},"Redis Response Caching in FastAPI","\u002Fasync-background-tasks-observability\u002Fcaching-strategies\u002Fredis-response-caching-in-fastapi","async-background-tasks-observability\u002Fcaching-strategies\u002Fredis-response-caching-in-fastapi\u002Findex",[175],{"title":171,"path":172,"stem":173},{"title":177,"path":178,"stem":179,"children":180},"Observability and Tracing in FastAPI","\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing","async-background-tasks-observability\u002Fobservability-and-tracing\u002Findex",[181,182,188],{"title":177,"path":178,"stem":179},{"title":183,"path":184,"stem":185,"children":186},"Instrumenting FastAPI with OpenTelemetry","\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing\u002Finstrumenting-fastapi-with-opentelemetry","async-background-tasks-observability\u002Fobservability-and-tracing\u002Finstrumenting-fastapi-with-opentelemetry\u002Findex",[187],{"title":183,"path":184,"stem":185},{"title":189,"path":190,"stem":191,"children":192},"Structured JSON Logging with Request IDs in FastAPI","\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing\u002Fstructured-json-logging-with-request-ids","async-background-tasks-observability\u002Fobservability-and-tracing\u002Fstructured-json-logging-with-request-ids\u002Findex",[193],{"title":189,"path":190,"stem":191},{"title":195,"path":196,"stem":197,"children":198},"Rate Limiting and Throttling in FastAPI","\u002Fasync-background-tasks-observability\u002Frate-limiting-throttling","async-background-tasks-observability\u002Frate-limiting-throttling\u002Findex",[199,200,206],{"title":195,"path":196,"stem":197},{"title":201,"path":202,"stem":203,"children":204},"FastAPI Rate Limiting with Redis and SlowAPI","\u002Fasync-background-tasks-observability\u002Frate-limiting-throttling\u002Ffastapi-rate-limiting-with-redis-slowapi","async-background-tasks-observability\u002Frate-limiting-throttling\u002Ffastapi-rate-limiting-with-redis-slowapi\u002Findex",[205],{"title":201,"path":202,"stem":203},{"title":207,"path":208,"stem":209,"children":210},"Per-User Token Bucket Throttling in FastAPI","\u002Fasync-background-tasks-observability\u002Frate-limiting-throttling\u002Fper-user-token-bucket-throttling","async-background-tasks-observability\u002Frate-limiting-throttling\u002Fper-user-token-bucket-throttling\u002Findex",[211],{"title":207,"path":208,"stem":209},{"title":213,"path":214,"stem":215,"children":216},"Core Architecture Routing Patterns","\u002Fcore-architecture-routing-patterns","core-architecture-routing-patterns",[217,220,232,250,268,280,292],{"title":218,"path":214,"stem":219},"FastAPI Core Architecture and Routing Patterns","core-architecture-routing-patterns\u002Findex",{"title":221,"path":222,"stem":223,"children":224},"Application Factory Patterns in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fapplication-factory-patterns","core-architecture-routing-patterns\u002Fapplication-factory-patterns\u002Findex",[225,226],{"title":221,"path":222,"stem":223},{"title":227,"path":228,"stem":229,"children":230},"FastAPI App Factory Pattern for Testing and Deployment","\u002Fcore-architecture-routing-patterns\u002Fapplication-factory-patterns\u002Ffastapi-app-factory-pattern-for-testing-and-deployment","core-architecture-routing-patterns\u002Fapplication-factory-patterns\u002Ffastapi-app-factory-pattern-for-testing-and-deployment\u002Findex",[231],{"title":227,"path":228,"stem":229},{"title":233,"path":234,"stem":235,"children":236},"Configuration Management in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fconfiguration-management","core-architecture-routing-patterns\u002Fconfiguration-management\u002Findex",[237,238,244],{"title":233,"path":234,"stem":235},{"title":239,"path":240,"stem":241,"children":242},"Managing Environment Variables with Pydantic Settings","\u002Fcore-architecture-routing-patterns\u002Fconfiguration-management\u002Fmanaging-environment-variables-with-pydantic-settings","core-architecture-routing-patterns\u002Fconfiguration-management\u002Fmanaging-environment-variables-with-pydantic-settings\u002Findex",[243],{"title":239,"path":240,"stem":241},{"title":245,"path":246,"stem":247,"children":248},"Pydantic Settings vs Dynaconf vs python-decouple","\u002Fcore-architecture-routing-patterns\u002Fconfiguration-management\u002Fpydantic-settings-vs-dynaconf-vs-python-decouple","core-architecture-routing-patterns\u002Fconfiguration-management\u002Fpydantic-settings-vs-dynaconf-vs-python-decouple\u002Findex",[249],{"title":245,"path":246,"stem":247},{"title":251,"path":252,"stem":253,"children":254},"Dependency Injection Strategies in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fdependency-injection-strategies","core-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Findex",[255,256,262],{"title":251,"path":252,"stem":253},{"title":257,"path":258,"stem":259,"children":260},"Best Practices for FastAPI Dependency Injection","\u002Fcore-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Fbest-practices-for-fastapi-dependency-injection","core-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Fbest-practices-for-fastapi-dependency-injection\u002Findex",[261],{"title":257,"path":258,"stem":259},{"title":263,"path":264,"stem":265,"children":266},"Fixing FastAPI Dependency Injection Circular Imports","\u002Fcore-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Ffastapi-dependency-injection-circular-import-fix","core-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Ffastapi-dependency-injection-circular-import-fix\u002Findex",[267],{"title":263,"path":264,"stem":265},{"title":269,"path":270,"stem":271,"children":272},"Error Handling and Global Exceptions in FastAPI","\u002Fcore-architecture-routing-patterns\u002Ferror-handling-global-exceptions","core-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Findex",[273,274],{"title":269,"path":270,"stem":271},{"title":275,"path":276,"stem":277,"children":278},"Global Exception Handlers for Consistent API Responses","\u002Fcore-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Fglobal-exception-handlers-for-consistent-api-responses","core-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Fglobal-exception-handlers-for-consistent-api-responses\u002Findex",[279],{"title":275,"path":276,"stem":277},{"title":281,"path":282,"stem":283,"children":284},"Middleware Implementation in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fmiddleware-implementation","core-architecture-routing-patterns\u002Fmiddleware-implementation\u002Findex",[285,286],{"title":281,"path":282,"stem":283},{"title":287,"path":288,"stem":289,"children":290},"Implementing Custom Middleware for Request Tracing","\u002Fcore-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fimplementing-custom-middleware-for-request-tracing","core-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fimplementing-custom-middleware-for-request-tracing\u002Findex",[291],{"title":287,"path":288,"stem":289},{"title":293,"path":294,"stem":295,"children":296},"Modular Router Organization in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fmodular-router-organization","core-architecture-routing-patterns\u002Fmodular-router-organization\u002Findex",[297,298,304],{"title":293,"path":294,"stem":295},{"title":299,"path":300,"stem":301,"children":302},"APIRouter Prefix vs Sub-Application Mounting in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fmodular-router-organization\u002Fapirouter-prefix-vs-sub-application-mounting","core-architecture-routing-patterns\u002Fmodular-router-organization\u002Fapirouter-prefix-vs-sub-application-mounting\u002Findex",[303],{"title":299,"path":300,"stem":301},{"title":305,"path":306,"stem":307,"children":308},"How to Structure Large FastAPI Projects for Scale","\u002Fcore-architecture-routing-patterns\u002Fmodular-router-organization\u002Fhow-to-structure-large-fastapi-projects-for-scale","core-architecture-routing-patterns\u002Fmodular-router-organization\u002Fhow-to-structure-large-fastapi-projects-for-scale\u002Findex",[309],{"title":305,"path":306,"stem":307},{"id":311,"title":171,"body":312,"description":1072,"extension":1073,"meta":1074,"navigation":426,"path":172,"seo":1116,"stem":173,"__hash__":1117},"content\u002Fasync-background-tasks-observability\u002Fcaching-strategies\u002Fredis-response-caching-in-fastapi\u002Findex.md",{"type":313,"value":314,"toc":1059},"minimark",[315,319,326,345,359,364,367,371,384,388,393,526,530,818,822,881,885,938,942,965,969,1029,1033,1055],[316,317,171],"h1",{"id":318},"redis-response-caching-in-fastapi",[320,321,322],"p",{},[323,324,325],"strong",{},"Key takeaways:",[327,328,329,333,336,339,342],"ul",{},[330,331,332],"li",{},"Use an async Redis client opened in lifespan so cache calls do not block the loop.",[330,334,335],{},"Build a stable key from route, path params, and sorted query string.",[330,337,338],{},"On a hit return cached JSON; on a miss compute, store with a TTL, and return.",[330,340,341],{},"Include the authenticated principal in the key for user-specific responses.",[330,343,344],{},"Treat Redis as optional — fall through to the database if it is unavailable.",[320,346,347,348,353,354,358],{},"This guide implements response-level caching from ",[349,350,352],"a",{"href":351},"\u002Fasync-background-tasks-observability\u002Fcaching-strategies\u002F","Caching Strategies",", and it pairs with ",[349,355,357],{"href":356},"\u002Fadvanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models\u002F","serialization performance"," since a hit skips re-serialization.",[360,361,363],"h2",{"id":362},"the-problem-this-solves","The Problem This Solves",[320,365,366],{},"A read-heavy endpoint that returns the same large payload to many clients recomputes and re-serializes it every time. Caching the serialized response in Redis turns repeated requests into a single fast memory read, cutting both latency and database load.",[360,368,370],{"id":369},"prerequisites","Prerequisites",[327,372,373,381],{},[330,374,375,376,380],{},"An async Redis client (",[377,378,379],"code",{},"redis.asyncio",").",[330,382,383],{},"A FastAPI app with a lifespan to own the connection.",[360,385,387],{"id":386},"step-by-step-implementation","Step-by-Step Implementation",[389,390,392],"h3",{"id":391},"_1-connect-redis-in-lifespan","1. Connect Redis in lifespan",[394,395,400],"pre",{"className":396,"code":397,"language":398,"meta":399,"style":399},"language-python shiki shiki-themes github-light","from contextlib import asynccontextmanager\n\nfrom fastapi import FastAPI\nfrom redis.asyncio import Redis\n\n\n@asynccontextmanager\nasync def lifespan(app: FastAPI):\n    app.state.redis = Redis.from_url(app.state.settings.redis_url, decode_responses=True)\n    yield\n    await app.state.redis.aclose()\n","python","",[377,401,402,421,428,441,454,459,464,471,486,511,517],{"__ignoreMap":399},[403,404,407,411,415,418],"span",{"class":405,"line":406},"line",1,[403,408,410],{"class":409},"sD7c4","from",[403,412,414],{"class":413},"sgsFI"," contextlib ",[403,416,417],{"class":409},"import",[403,419,420],{"class":413}," asynccontextmanager\n",[403,422,424],{"class":405,"line":423},2,[403,425,427],{"emptyLinePlaceholder":426},true,"\n",[403,429,431,433,436,438],{"class":405,"line":430},3,[403,432,410],{"class":409},[403,434,435],{"class":413}," fastapi ",[403,437,417],{"class":409},[403,439,440],{"class":413}," FastAPI\n",[403,442,444,446,449,451],{"class":405,"line":443},4,[403,445,410],{"class":409},[403,447,448],{"class":413}," redis.asyncio ",[403,450,417],{"class":409},[403,452,453],{"class":413}," Redis\n",[403,455,457],{"class":405,"line":456},5,[403,458,427],{"emptyLinePlaceholder":426},[403,460,462],{"class":405,"line":461},6,[403,463,427],{"emptyLinePlaceholder":426},[403,465,467],{"class":405,"line":466},7,[403,468,470],{"class":469},"s7eDp","@asynccontextmanager\n",[403,472,474,477,480,483],{"class":405,"line":473},8,[403,475,476],{"class":409},"async",[403,478,479],{"class":409}," def",[403,481,482],{"class":469}," lifespan",[403,484,485],{"class":413},"(app: FastAPI):\n",[403,487,489,492,495,498,502,504,508],{"class":405,"line":488},9,[403,490,491],{"class":413},"    app.state.redis ",[403,493,494],{"class":409},"=",[403,496,497],{"class":413}," Redis.from_url(app.state.settings.redis_url, ",[403,499,501],{"class":500},"sqxcx","decode_responses",[403,503,494],{"class":409},[403,505,507],{"class":506},"sYu0t","True",[403,509,510],{"class":413},")\n",[403,512,514],{"class":405,"line":513},10,[403,515,516],{"class":409},"    yield\n",[403,518,520,523],{"class":405,"line":519},11,[403,521,522],{"class":409},"    await",[403,524,525],{"class":413}," app.state.redis.aclose()\n",[389,527,529],{"id":528},"_2-build-a-stable-key-and-cache-aside-helper","2. Build a stable key and cache-aside helper",[394,531,533],{"className":396,"code":532,"language":398,"meta":399,"style":399},"import json\nfrom urllib.parse import urlencode\n\nfrom fastapi import Request\nfrom redis.asyncio import Redis\n\n\ndef cache_key(request: Request) -> str:\n    # Sorted query string → equivalent queries share one key.\n    qs = urlencode(sorted(request.query_params.multi_items()))\n    return f\"resp:{request.url.path}?{qs}\"\n\n\nasync def cached_json(redis: Redis, key: str, loader, ttl: int = 120):\n    try:\n        if hit := await redis.get(key):\n            return json.loads(hit)               # Hit: skip compute + serialize.\n    except Exception:\n        pass                                     # Cache optional: fall through.\n    value = await loader()\n    try:\n        await redis.set(key, json.dumps(value), ex=ttl)\n    except Exception:\n        pass\n    return value\n",[377,534,535,542,554,558,569,579,583,587,604,610,626,660,665,670,700,708,726,738,749,758,771,778,795,804,810],{"__ignoreMap":399},[403,536,537,539],{"class":405,"line":406},[403,538,417],{"class":409},[403,540,541],{"class":413}," json\n",[403,543,544,546,549,551],{"class":405,"line":423},[403,545,410],{"class":409},[403,547,548],{"class":413}," urllib.parse ",[403,550,417],{"class":409},[403,552,553],{"class":413}," urlencode\n",[403,555,556],{"class":405,"line":430},[403,557,427],{"emptyLinePlaceholder":426},[403,559,560,562,564,566],{"class":405,"line":443},[403,561,410],{"class":409},[403,563,435],{"class":413},[403,565,417],{"class":409},[403,567,568],{"class":413}," Request\n",[403,570,571,573,575,577],{"class":405,"line":456},[403,572,410],{"class":409},[403,574,448],{"class":413},[403,576,417],{"class":409},[403,578,453],{"class":413},[403,580,581],{"class":405,"line":461},[403,582,427],{"emptyLinePlaceholder":426},[403,584,585],{"class":405,"line":466},[403,586,427],{"emptyLinePlaceholder":426},[403,588,589,592,595,598,601],{"class":405,"line":473},[403,590,591],{"class":409},"def",[403,593,594],{"class":469}," cache_key",[403,596,597],{"class":413},"(request: Request) -> ",[403,599,600],{"class":506},"str",[403,602,603],{"class":413},":\n",[403,605,606],{"class":405,"line":488},[403,607,609],{"class":608},"sAwPA","    # Sorted query string → equivalent queries share one key.\n",[403,611,612,615,617,620,623],{"class":405,"line":513},[403,613,614],{"class":413},"    qs ",[403,616,494],{"class":409},[403,618,619],{"class":413}," urlencode(",[403,621,622],{"class":506},"sorted",[403,624,625],{"class":413},"(request.query_params.multi_items()))\n",[403,627,628,631,634,638,641,644,647,650,652,655,657],{"class":405,"line":519},[403,629,630],{"class":409},"    return",[403,632,633],{"class":409}," f",[403,635,637],{"class":636},"sYBdl","\"resp:",[403,639,640],{"class":506},"{",[403,642,643],{"class":413},"request.url.path",[403,645,646],{"class":506},"}",[403,648,649],{"class":636},"?",[403,651,640],{"class":506},[403,653,654],{"class":413},"qs",[403,656,646],{"class":506},[403,658,659],{"class":636},"\"\n",[403,661,663],{"class":405,"line":662},12,[403,664,427],{"emptyLinePlaceholder":426},[403,666,668],{"class":405,"line":667},13,[403,669,427],{"emptyLinePlaceholder":426},[403,671,673,675,677,680,683,685,688,691,694,697],{"class":405,"line":672},14,[403,674,476],{"class":409},[403,676,479],{"class":409},[403,678,679],{"class":469}," cached_json",[403,681,682],{"class":413},"(redis: Redis, key: ",[403,684,600],{"class":506},[403,686,687],{"class":413},", loader, ttl: ",[403,689,690],{"class":506},"int",[403,692,693],{"class":409}," =",[403,695,696],{"class":506}," 120",[403,698,699],{"class":413},"):\n",[403,701,703,706],{"class":405,"line":702},15,[403,704,705],{"class":409},"    try",[403,707,603],{"class":413},[403,709,711,714,717,720,723],{"class":405,"line":710},16,[403,712,713],{"class":409},"        if",[403,715,716],{"class":413}," hit ",[403,718,719],{"class":409},":=",[403,721,722],{"class":409}," await",[403,724,725],{"class":413}," redis.get(key):\n",[403,727,729,732,735],{"class":405,"line":728},17,[403,730,731],{"class":409},"            return",[403,733,734],{"class":413}," json.loads(hit)               ",[403,736,737],{"class":608},"# Hit: skip compute + serialize.\n",[403,739,741,744,747],{"class":405,"line":740},18,[403,742,743],{"class":409},"    except",[403,745,746],{"class":506}," Exception",[403,748,603],{"class":413},[403,750,752,755],{"class":405,"line":751},19,[403,753,754],{"class":409},"        pass",[403,756,757],{"class":608},"                                     # Cache optional: fall through.\n",[403,759,761,764,766,768],{"class":405,"line":760},20,[403,762,763],{"class":413},"    value ",[403,765,494],{"class":409},[403,767,722],{"class":409},[403,769,770],{"class":413}," loader()\n",[403,772,774,776],{"class":405,"line":773},21,[403,775,705],{"class":409},[403,777,603],{"class":413},[403,779,781,784,787,790,792],{"class":405,"line":780},22,[403,782,783],{"class":409},"        await",[403,785,786],{"class":413}," redis.set(key, json.dumps(value), ",[403,788,789],{"class":500},"ex",[403,791,494],{"class":409},[403,793,794],{"class":413},"ttl)\n",[403,796,798,800,802],{"class":405,"line":797},23,[403,799,743],{"class":409},[403,801,746],{"class":506},[403,803,603],{"class":413},[403,805,807],{"class":405,"line":806},24,[403,808,809],{"class":409},"        pass\n",[403,811,813,815],{"class":405,"line":812},25,[403,814,630],{"class":409},[403,816,817],{"class":413}," value\n",[389,819,821],{"id":820},"_3-use-it-in-a-route","3. Use it in a route",[394,823,825],{"className":396,"code":824,"language":398,"meta":399,"style":399},"@router.get(\"\u002Fcatalog\")\nasync def catalog(request: Request):\n    redis = request.app.state.redis\n    return await cached_json(redis, cache_key(request), load_catalog, ttl=300)\n",[377,826,827,840,852,862],{"__ignoreMap":399},[403,828,829,832,835,838],{"class":405,"line":406},[403,830,831],{"class":469},"@router.get",[403,833,834],{"class":413},"(",[403,836,837],{"class":636},"\"\u002Fcatalog\"",[403,839,510],{"class":413},[403,841,842,844,846,849],{"class":405,"line":423},[403,843,476],{"class":409},[403,845,479],{"class":409},[403,847,848],{"class":469}," catalog",[403,850,851],{"class":413},"(request: Request):\n",[403,853,854,857,859],{"class":405,"line":430},[403,855,856],{"class":413},"    redis ",[403,858,494],{"class":409},[403,860,861],{"class":413}," request.app.state.redis\n",[403,863,864,866,868,871,874,876,879],{"class":405,"line":443},[403,865,630],{"class":409},[403,867,722],{"class":409},[403,869,870],{"class":413}," cached_json(redis, cache_key(request), load_catalog, ",[403,872,873],{"class":500},"ttl",[403,875,494],{"class":409},[403,877,878],{"class":506},"300",[403,880,510],{"class":413},[389,882,884],{"id":883},"_4-invalidate-on-writes","4. Invalidate on writes",[394,886,888],{"className":396,"code":887,"language":398,"meta":399,"style":399},"@router.post(\"\u002Fcatalog\u002Fitems\")\nasync def add_item(item: ItemIn, request: Request):\n    await save_item(item)\n    # Drop the cached listing so the next read reflects the new item.\n    await request.app.state.redis.delete(\"resp:\u002Fcatalog?\")\n",[377,889,890,902,914,921,926],{"__ignoreMap":399},[403,891,892,895,897,900],{"class":405,"line":406},[403,893,894],{"class":469},"@router.post",[403,896,834],{"class":413},[403,898,899],{"class":636},"\"\u002Fcatalog\u002Fitems\"",[403,901,510],{"class":413},[403,903,904,906,908,911],{"class":405,"line":423},[403,905,476],{"class":409},[403,907,479],{"class":409},[403,909,910],{"class":469}," add_item",[403,912,913],{"class":413},"(item: ItemIn, request: Request):\n",[403,915,916,918],{"class":405,"line":430},[403,917,522],{"class":409},[403,919,920],{"class":413}," save_item(item)\n",[403,922,923],{"class":405,"line":443},[403,924,925],{"class":608},"    # Drop the cached listing so the next read reflects the new item.\n",[403,927,928,930,933,936],{"class":405,"line":456},[403,929,522],{"class":409},[403,931,932],{"class":413}," request.app.state.redis.delete(",[403,934,935],{"class":636},"\"resp:\u002Fcatalog?\"",[403,937,510],{"class":413},[360,939,941],{"id":940},"edge-cases-and-gotchas","Edge Cases and Gotchas",[327,943,944,950,956],{},[330,945,946,949],{},[323,947,948],{},"User-specific data."," Include the principal in the key, or you will serve one user's data to another.",[330,951,952,955],{},[323,953,954],{},"Large values."," Cap cached payload size; very large values pressure Redis memory.",[330,957,958,961,962,964],{},[323,959,960],{},"Thundering herd."," A hot key expiring under load can stampede; add a short lock, per ",[349,963,352],{"href":351},".",[360,966,968],{"id":967},"verification","Verification",[394,970,972],{"className":396,"code":971,"language":398,"meta":399,"style":399},"async def test_second_request_is_cached(client, db_spy):\n    client.get(\"\u002Fcatalog\")        # Miss → loads.\n    db_spy.reset()\n    client.get(\"\u002Fcatalog\")        # Hit → no DB load.\n    assert db_spy.load_count == 0\n",[377,973,974,986,999,1004,1015],{"__ignoreMap":399},[403,975,976,978,980,983],{"class":405,"line":406},[403,977,476],{"class":409},[403,979,479],{"class":409},[403,981,982],{"class":469}," test_second_request_is_cached",[403,984,985],{"class":413},"(client, db_spy):\n",[403,987,988,991,993,996],{"class":405,"line":423},[403,989,990],{"class":413},"    client.get(",[403,992,837],{"class":636},[403,994,995],{"class":413},")        ",[403,997,998],{"class":608},"# Miss → loads.\n",[403,1000,1001],{"class":405,"line":430},[403,1002,1003],{"class":413},"    db_spy.reset()\n",[403,1005,1006,1008,1010,1012],{"class":405,"line":443},[403,1007,990],{"class":413},[403,1009,837],{"class":636},[403,1011,995],{"class":413},[403,1013,1014],{"class":608},"# Hit → no DB load.\n",[403,1016,1017,1020,1023,1026],{"class":405,"line":456},[403,1018,1019],{"class":409},"    assert",[403,1021,1022],{"class":413}," db_spy.load_count ",[403,1024,1025],{"class":409},"==",[403,1027,1028],{"class":506}," 0\n",[360,1030,1032],{"id":1031},"related-reading","Related Reading",[327,1034,1035,1043],{},[330,1036,1037,1040,1041,964],{},[323,1038,1039],{},"Up to the topic:"," ",[349,1042,352],{"href":351},[330,1044,1045,1040,1048,1051,1052,964],{},[323,1046,1047],{},"Related guides:",[349,1049,165],{"href":1050},"\u002Fasync-background-tasks-observability\u002Fcaching-strategies\u002Fcache-invalidation-patterns-in-fastapi\u002F"," and ",[349,1053,49],{"href":1054},"\u002Fadvanced-pydantic-validation-serialization\u002Fnested-model-serialization\u002Fhandling-deeply-nested-json-models-efficiently\u002F",[1056,1057,1058],"style",{},"html pre.shiki code .sD7c4, html code.shiki .sD7c4{--shiki-default:#D73A49}html pre.shiki code .sgsFI, html code.shiki .sgsFI{--shiki-default:#24292E}html pre.shiki code .s7eDp, html code.shiki .s7eDp{--shiki-default:#6F42C1}html pre.shiki code .sqxcx, html code.shiki .sqxcx{--shiki-default:#E36209}html pre.shiki code .sYu0t, html code.shiki .sYu0t{--shiki-default:#005CC5}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .sYBdl, html code.shiki .sYBdl{--shiki-default:#032F62}",{"title":399,"searchDepth":423,"depth":423,"links":1060},[1061,1062,1063,1069,1070,1071],{"id":362,"depth":423,"text":363},{"id":369,"depth":423,"text":370},{"id":386,"depth":423,"text":387,"children":1064},[1065,1066,1067,1068],{"id":391,"depth":430,"text":392},{"id":528,"depth":430,"text":529},{"id":820,"depth":430,"text":821},{"id":883,"depth":430,"text":884},{"id":940,"depth":423,"text":941},{"id":967,"depth":423,"text":968},{"id":1031,"depth":423,"text":1032},"Cache FastAPI responses in Redis: a cache-aside dependency, keying by path and query, TTLs, serving cached JSON, conditional caching, and invalidation on writes.","md",{"slug":318,"type":1075,"breadcrumb":1076,"datePublished":1086,"dateModified":1087,"howto":1088,"faq":1106},"long_tail",[1077,1080,1083,1084],{"label":1078,"path":1079},"Home","\u002F",{"label":1081,"path":1082},"Async, Background Tasks & Observability","\u002Fasync-background-tasks-observability\u002F",{"label":352,"path":351},{"label":171,"path":1085},"\u002Fasync-background-tasks-observability\u002Fcaching-strategies\u002Fredis-response-caching-in-fastapi\u002F","2026-02-25","2026-06-18",{"name":1089,"steps":1090},"Cache FastAPI responses in Redis",[1091,1094,1097,1100,1103],{"name":1092,"text":1093},"Connect an async Redis client","Open an async Redis connection in lifespan and store it on app.state.",{"name":1095,"text":1096},"Build a stable cache key","Derive the key from the route, path params, and sorted query string.",{"name":1098,"text":1099},"Check then populate","Return cached JSON on a hit; on a miss, compute, store with a TTL, and return.",{"name":1101,"text":1102},"Set a sensible TTL","Choose the TTL from how stale the data may safely be.",{"name":1104,"text":1105},"Invalidate on writes","Delete affected keys when the underlying data changes.",[1107,1110,1113],{"q":1108,"a":1109},"Should I cache the serialized JSON or the model?","For response caching, store the serialized JSON so a hit also skips re-serialization, which is the expensive part for large nested payloads. The trade-off is that the cached value is tied to that response shape; if several endpoints need the same data in different shapes, caching the model or row is more reusable.",{"q":1111,"a":1112},"How do I build a cache key that does not collide?","Combine the route identity, the path parameters, and the query string with its keys sorted so equivalent queries map to the same key. For user-specific responses, include the authenticated principal in the key so one user's cached data never serves another.",{"q":1114,"a":1115},"What happens if Redis is down?","Treat the cache as optional: on a Redis error, log it and fall through to the database so the request still succeeds, just without the cache speedup. A cache outage should degrade latency, never availability, so never let a cache failure raise to the client.",{"title":171,"description":1072},"6kCjMaA4-I-XJEVjPpVikm-8Gb7musrzdzxz6YEy4xs",[1119,1119],null,1781809863622]