[{"data":1,"prerenderedAt":2535},["ShallowReactive",2],{"nav":3,"page-\u002Fcore-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fmiddleware-execution-order\u002F":580,"surround-\u002Fcore-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fmiddleware-execution-order\u002F":2534},[4,186,386],{"title":5,"path":6,"stem":7,"children":8},"Advanced Pydantic Validation Serialization","\u002Fadvanced-pydantic-validation-serialization","advanced-pydantic-validation-serialization",[9,12,42,66,90,114,150,174],{"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,30,36],{"title":13,"path":14,"stem":15},{"title":19,"path":20,"stem":21,"children":22},"Before, After and Wrap Validators in Pydantic v2","\u002Fadvanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fbefore-after-and-wrap-validators","advanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fbefore-after-and-wrap-validators\u002Findex",[23],{"title":19,"path":20,"stem":21},{"title":25,"path":26,"stem":27,"children":28},"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",[29],{"title":25,"path":26,"stem":27},{"title":31,"path":32,"stem":33,"children":34},"Cross-Field Validation Patterns in Pydantic v2","\u002Fadvanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fcross-field-validation-patterns","advanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fcross-field-validation-patterns\u002Findex",[35],{"title":31,"path":32,"stem":33},{"title":37,"path":38,"stem":39,"children":40},"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",[41],{"title":37,"path":38,"stem":39},{"title":43,"path":44,"stem":45,"children":46},"JSON Schema Customization in Pydantic and FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fjson-schema-customization","advanced-pydantic-validation-serialization\u002Fjson-schema-customization\u002Findex",[47,48,54,60],{"title":43,"path":44,"stem":45},{"title":49,"path":50,"stem":51,"children":52},"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",[53],{"title":49,"path":50,"stem":51},{"title":55,"path":56,"stem":57,"children":58},"Discriminated Unions in OpenAPI with Pydantic","\u002Fadvanced-pydantic-validation-serialization\u002Fjson-schema-customization\u002Fdiscriminated-unions-in-openapi","advanced-pydantic-validation-serialization\u002Fjson-schema-customization\u002Fdiscriminated-unions-in-openapi\u002Findex",[59],{"title":55,"path":56,"stem":57},{"title":61,"path":62,"stem":63,"children":64},"Examples in the OpenAPI Schema with FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fjson-schema-customization\u002Fexamples-in-openapi-schema","advanced-pydantic-validation-serialization\u002Fjson-schema-customization\u002Fexamples-in-openapi-schema\u002Findex",[65],{"title":61,"path":62,"stem":63},{"title":67,"path":68,"stem":69,"children":70},"Nested Model Serialization in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fnested-model-serialization","advanced-pydantic-validation-serialization\u002Fnested-model-serialization\u002Findex",[71,72,78,84],{"title":67,"path":68,"stem":69},{"title":73,"path":74,"stem":75,"children":76},"Excluding Fields Per Endpoint in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fnested-model-serialization\u002Fexcluding-fields-per-endpoint","advanced-pydantic-validation-serialization\u002Fnested-model-serialization\u002Fexcluding-fields-per-endpoint\u002Findex",[77],{"title":73,"path":74,"stem":75},{"title":79,"path":80,"stem":81,"children":82},"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",[83],{"title":79,"path":80,"stem":81},{"title":85,"path":86,"stem":87,"children":88},"Self-Referencing and Recursive Models in Pydantic v2","\u002Fadvanced-pydantic-validation-serialization\u002Fnested-model-serialization\u002Fself-referencing-and-recursive-models","advanced-pydantic-validation-serialization\u002Fnested-model-serialization\u002Fself-referencing-and-recursive-models\u002Findex",[89],{"title":85,"path":86,"stem":87},{"title":91,"path":92,"stem":93,"children":94},"Performance Optimization for Pydantic Models in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models","advanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models\u002Findex",[95,96,102,108],{"title":91,"path":92,"stem":93},{"title":97,"path":98,"stem":99,"children":100},"model_construct and When to Skip Validation in Pydantic","\u002Fadvanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models\u002Fmodel-construct-when-to-skip-validation","advanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models\u002Fmodel-construct-when-to-skip-validation\u002Findex",[101],{"title":97,"path":98,"stem":99},{"title":103,"path":104,"stem":105,"children":106},"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",[107],{"title":103,"path":104,"stem":105},{"title":109,"path":110,"stem":111,"children":112},"TypeAdapter for Non-Model Types in Pydantic","\u002Fadvanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models\u002Ftypeadapter-for-non-model-types","advanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models\u002Ftypeadapter-for-non-model-types\u002Findex",[113],{"title":109,"path":110,"stem":111},{"title":115,"path":116,"stem":117,"children":118},"Pydantic V2 Migration Guide for FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide","advanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Findex",[119,120,126,132,138,144],{"title":115,"path":116,"stem":117},{"title":121,"path":122,"stem":123,"children":124},"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",[125],{"title":121,"path":122,"stem":123},{"title":127,"path":128,"stem":129,"children":130},"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",[131],{"title":127,"path":128,"stem":129},{"title":133,"path":134,"stem":135,"children":136},"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",[137],{"title":133,"path":134,"stem":135},{"title":139,"path":140,"stem":141,"children":142},"Replacing json_encoders with field_serializer in Pydantic v2","\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Freplacing-json-encoders-with-field-serializer","advanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Freplacing-json-encoders-with-field-serializer\u002Findex",[143],{"title":139,"path":140,"stem":141},{"title":145,"path":146,"stem":147,"children":148},"Migrating @root_validator to @model_validator in Pydantic v2","\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Froot-validator-to-model-validator","advanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Froot-validator-to-model-validator\u002Findex",[149],{"title":145,"path":146,"stem":147},{"title":151,"path":152,"stem":153,"children":154},"Request Validation Patterns in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Frequest-validation-patterns","advanced-pydantic-validation-serialization\u002Frequest-validation-patterns\u002Findex",[155,156,162,168],{"title":151,"path":152,"stem":153},{"title":157,"path":158,"stem":159,"children":160},"Optional vs Nullable Fields in Pydantic and FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Frequest-validation-patterns\u002Foptional-vs-nullable-fields","advanced-pydantic-validation-serialization\u002Frequest-validation-patterns\u002Foptional-vs-nullable-fields\u002Findex",[161],{"title":157,"path":158,"stem":159},{"title":163,"path":164,"stem":165,"children":166},"Query, Path and Body Parameter Validation in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Frequest-validation-patterns\u002Fquery-path-and-body-parameter-validation","advanced-pydantic-validation-serialization\u002Frequest-validation-patterns\u002Fquery-path-and-body-parameter-validation\u002Findex",[167],{"title":163,"path":164,"stem":165},{"title":169,"path":170,"stem":171,"children":172},"Validating File Uploads and Forms in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Frequest-validation-patterns\u002Fvalidating-file-uploads-and-forms","advanced-pydantic-validation-serialization\u002Frequest-validation-patterns\u002Fvalidating-file-uploads-and-forms\u002Findex",[173],{"title":169,"path":170,"stem":171},{"title":175,"path":176,"stem":177,"children":178},"Type Hinting and IDE Integration in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Ftype-hinting-ide-integration","advanced-pydantic-validation-serialization\u002Ftype-hinting-ide-integration\u002Findex",[179,180],{"title":175,"path":176,"stem":177},{"title":181,"path":182,"stem":183,"children":184},"Annotated Dependencies and Reusable Types in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Ftype-hinting-ide-integration\u002Fannotated-dependencies-and-reusable-types","advanced-pydantic-validation-serialization\u002Ftype-hinting-ide-integration\u002Fannotated-dependencies-and-reusable-types\u002Findex",[185],{"title":181,"path":182,"stem":183},{"title":187,"path":188,"stem":189,"children":190},"Async Background Tasks Observability","\u002Fasync-background-tasks-observability","async-background-tasks-observability",[191,194,224,254,284,308,338,362],{"title":192,"path":188,"stem":193},"Async, Background Tasks, and Observability in FastAPI","async-background-tasks-observability\u002Findex",{"title":195,"path":196,"stem":197,"children":198},"Async Correctness and Concurrency in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-correctness-concurrency","async-background-tasks-observability\u002Fasync-correctness-concurrency\u002Findex",[199,200,206,212,218],{"title":195,"path":196,"stem":197},{"title":201,"path":202,"stem":203,"children":204},"Concurrent Requests with asyncio.gather in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-correctness-concurrency\u002Fconcurrent-requests-with-asyncio-gather","async-background-tasks-observability\u002Fasync-correctness-concurrency\u002Fconcurrent-requests-with-asyncio-gather\u002Findex",[205],{"title":201,"path":202,"stem":203},{"title":207,"path":208,"stem":209,"children":210},"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",[211],{"title":207,"path":208,"stem":209},{"title":213,"path":214,"stem":215,"children":216},"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",[217],{"title":213,"path":214,"stem":215},{"title":219,"path":220,"stem":221,"children":222},"Running Sync Code in a Threadpool in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-correctness-concurrency\u002Frunning-sync-code-in-a-threadpool","async-background-tasks-observability\u002Fasync-correctness-concurrency\u002Frunning-sync-code-in-a-threadpool\u002Findex",[223],{"title":219,"path":220,"stem":221},{"title":225,"path":226,"stem":227,"children":228},"Async Database Sessions in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-database-sessions","async-background-tasks-observability\u002Fasync-database-sessions\u002Findex",[229,230,236,242,248],{"title":225,"path":226,"stem":227},{"title":231,"path":232,"stem":233,"children":234},"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",[235],{"title":231,"path":232,"stem":233},{"title":237,"path":238,"stem":239,"children":240},"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",[241],{"title":237,"path":238,"stem":239},{"title":243,"path":244,"stem":245,"children":246},"Testing with Async Database Fixtures in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-database-sessions\u002Ftesting-with-async-database-fixtures","async-background-tasks-observability\u002Fasync-database-sessions\u002Ftesting-with-async-database-fixtures\u002Findex",[247],{"title":243,"path":244,"stem":245},{"title":249,"path":250,"stem":251,"children":252},"Transaction Management and Rollback in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-database-sessions\u002Ftransaction-management-and-rollback","async-background-tasks-observability\u002Fasync-database-sessions\u002Ftransaction-management-and-rollback\u002Findex",[253],{"title":249,"path":250,"stem":251},{"title":255,"path":256,"stem":257,"children":258},"Background Task Processing in FastAPI","\u002Fasync-background-tasks-observability\u002Fbackground-task-processing","async-background-tasks-observability\u002Fbackground-task-processing\u002Findex",[259,260,266,272,278],{"title":255,"path":256,"stem":257},{"title":261,"path":262,"stem":263,"children":264},"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",[265],{"title":261,"path":262,"stem":263},{"title":267,"path":268,"stem":269,"children":270},"Retry and Idempotency for FastAPI Background Tasks","\u002Fasync-background-tasks-observability\u002Fbackground-task-processing\u002Fretry-and-idempotency-for-tasks","async-background-tasks-observability\u002Fbackground-task-processing\u002Fretry-and-idempotency-for-tasks\u002Findex",[271],{"title":267,"path":268,"stem":269},{"title":273,"path":274,"stem":275,"children":276},"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",[277],{"title":273,"path":274,"stem":275},{"title":279,"path":280,"stem":281,"children":282},"When FastAPI BackgroundTasks Silently Fails","\u002Fasync-background-tasks-observability\u002Fbackground-task-processing\u002Fwhen-backgroundtasks-silently-fails","async-background-tasks-observability\u002Fbackground-task-processing\u002Fwhen-backgroundtasks-silently-fails\u002Findex",[283],{"title":279,"path":280,"stem":281},{"title":285,"path":286,"stem":287,"children":288},"Caching Strategies in FastAPI","\u002Fasync-background-tasks-observability\u002Fcaching-strategies","async-background-tasks-observability\u002Fcaching-strategies\u002Findex",[289,290,296,302],{"title":285,"path":286,"stem":287},{"title":291,"path":292,"stem":293,"children":294},"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",[295],{"title":291,"path":292,"stem":293},{"title":297,"path":298,"stem":299,"children":300},"Caching Dependency Results in FastAPI","\u002Fasync-background-tasks-observability\u002Fcaching-strategies\u002Fcaching-dependency-results","async-background-tasks-observability\u002Fcaching-strategies\u002Fcaching-dependency-results\u002Findex",[301],{"title":297,"path":298,"stem":299},{"title":303,"path":304,"stem":305,"children":306},"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",[307],{"title":303,"path":304,"stem":305},{"title":309,"path":310,"stem":311,"children":312},"Observability and Tracing in FastAPI","\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing","async-background-tasks-observability\u002Fobservability-and-tracing\u002Findex",[313,314,320,326,332],{"title":309,"path":310,"stem":311},{"title":315,"path":316,"stem":317,"children":318},"Correlating Logs, Traces and Errors in FastAPI","\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing\u002Fcorrelating-logs-traces-and-errors","async-background-tasks-observability\u002Fobservability-and-tracing\u002Fcorrelating-logs-traces-and-errors\u002Findex",[319],{"title":315,"path":316,"stem":317},{"title":321,"path":322,"stem":323,"children":324},"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",[325],{"title":321,"path":322,"stem":323},{"title":327,"path":328,"stem":329,"children":330},"Prometheus Metrics for FastAPI","\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing\u002Fprometheus-metrics-for-fastapi","async-background-tasks-observability\u002Fobservability-and-tracing\u002Fprometheus-metrics-for-fastapi\u002Findex",[331],{"title":327,"path":328,"stem":329},{"title":333,"path":334,"stem":335,"children":336},"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",[337],{"title":333,"path":334,"stem":335},{"title":339,"path":340,"stem":341,"children":342},"Rate Limiting and Throttling in FastAPI","\u002Fasync-background-tasks-observability\u002Frate-limiting-throttling","async-background-tasks-observability\u002Frate-limiting-throttling\u002Findex",[343,344,350,356],{"title":339,"path":340,"stem":341},{"title":345,"path":346,"stem":347,"children":348},"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",[349],{"title":345,"path":346,"stem":347},{"title":351,"path":352,"stem":353,"children":354},"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",[355],{"title":351,"path":352,"stem":353},{"title":357,"path":358,"stem":359,"children":360},"Rate Limit Headers and 429 Responses in FastAPI","\u002Fasync-background-tasks-observability\u002Frate-limiting-throttling\u002Frate-limit-headers-and-429-responses","async-background-tasks-observability\u002Frate-limiting-throttling\u002Frate-limit-headers-and-429-responses\u002Findex",[361],{"title":357,"path":358,"stem":359},{"title":363,"path":364,"stem":365,"children":366},"Testing FastAPI Applications","\u002Fasync-background-tasks-observability\u002Ftesting-fastapi-applications","async-background-tasks-observability\u002Ftesting-fastapi-applications\u002Findex",[367,368,374,380],{"title":363,"path":364,"stem":365},{"title":369,"path":370,"stem":371,"children":372},"Mocking External Services in FastAPI Tests","\u002Fasync-background-tasks-observability\u002Ftesting-fastapi-applications\u002Fmocking-external-services-in-tests","async-background-tasks-observability\u002Ftesting-fastapi-applications\u002Fmocking-external-services-in-tests\u002Findex",[373],{"title":369,"path":370,"stem":371},{"title":375,"path":376,"stem":377,"children":378},"TestClient vs httpx AsyncClient in FastAPI","\u002Fasync-background-tasks-observability\u002Ftesting-fastapi-applications\u002Ftestclient-vs-httpx-asyncclient","async-background-tasks-observability\u002Ftesting-fastapi-applications\u002Ftestclient-vs-httpx-asyncclient\u002Findex",[379],{"title":375,"path":376,"stem":377},{"title":381,"path":382,"stem":383,"children":384},"Testing Async FastAPI Endpoints with pytest-asyncio","\u002Fasync-background-tasks-observability\u002Ftesting-fastapi-applications\u002Ftesting-async-endpoints-with-pytest-asyncio","async-background-tasks-observability\u002Ftesting-fastapi-applications\u002Ftesting-async-endpoints-with-pytest-asyncio\u002Findex",[385],{"title":381,"path":382,"stem":383},{"title":387,"path":388,"stem":389,"children":390},"Core Architecture Routing Patterns","\u002Fcore-architecture-routing-patterns","core-architecture-routing-patterns",[391,394,412,436,472,496,526,556],{"title":392,"path":388,"stem":393},"FastAPI Core Architecture and Routing Patterns","core-architecture-routing-patterns\u002Findex",{"title":395,"path":396,"stem":397,"children":398},"Application Factory Patterns in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fapplication-factory-patterns","core-architecture-routing-patterns\u002Fapplication-factory-patterns\u002Findex",[399,400,406],{"title":395,"path":396,"stem":397},{"title":401,"path":402,"stem":403,"children":404},"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",[405],{"title":401,"path":402,"stem":403},{"title":407,"path":408,"stem":409,"children":410},"Lifespan Events vs Startup and Shutdown in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fapplication-factory-patterns\u002Flifespan-events-vs-startup-shutdown","core-architecture-routing-patterns\u002Fapplication-factory-patterns\u002Flifespan-events-vs-startup-shutdown\u002Findex",[411],{"title":407,"path":408,"stem":409},{"title":413,"path":414,"stem":415,"children":416},"Configuration Management in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fconfiguration-management","core-architecture-routing-patterns\u002Fconfiguration-management\u002Findex",[417,418,424,430],{"title":413,"path":414,"stem":415},{"title":419,"path":420,"stem":421,"children":422},"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",[423],{"title":419,"path":420,"stem":421},{"title":425,"path":426,"stem":427,"children":428},"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",[429],{"title":425,"path":426,"stem":427},{"title":431,"path":432,"stem":433,"children":434},"Secrets and .env Files Per Environment in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fconfiguration-management\u002Fsecrets-and-env-files-per-environment","core-architecture-routing-patterns\u002Fconfiguration-management\u002Fsecrets-and-env-files-per-environment\u002Findex",[435],{"title":431,"path":432,"stem":433},{"title":437,"path":438,"stem":439,"children":440},"Dependency Injection Strategies in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fdependency-injection-strategies","core-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Findex",[441,442,448,454,460,466],{"title":437,"path":438,"stem":439},{"title":443,"path":444,"stem":445,"children":446},"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",[447],{"title":443,"path":444,"stem":445},{"title":449,"path":450,"stem":451,"children":452},"Dependency Caching and use_cache in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Fdependency-caching-and-use-cache","core-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Fdependency-caching-and-use-cache\u002Findex",[453],{"title":449,"path":450,"stem":451},{"title":455,"path":456,"stem":457,"children":458},"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",[459],{"title":455,"path":456,"stem":457},{"title":461,"path":462,"stem":463,"children":464},"Overriding Dependencies in FastAPI Tests","\u002Fcore-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Foverriding-dependencies-in-tests","core-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Foverriding-dependencies-in-tests\u002Findex",[465],{"title":461,"path":462,"stem":463},{"title":467,"path":468,"stem":469,"children":470},"Yield Dependencies and Cleanup Order in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Fyield-dependencies-and-cleanup-order","core-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Fyield-dependencies-and-cleanup-order\u002Findex",[471],{"title":467,"path":468,"stem":469},{"title":473,"path":474,"stem":475,"children":476},"Error Handling and Global Exceptions in FastAPI","\u002Fcore-architecture-routing-patterns\u002Ferror-handling-global-exceptions","core-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Findex",[477,478,484,490],{"title":473,"path":474,"stem":475},{"title":479,"path":480,"stem":481,"children":482},"Customising Validation Error Responses in FastAPI","\u002Fcore-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Fcustomising-validation-error-responses","core-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Fcustomising-validation-error-responses\u002Findex",[483],{"title":479,"path":480,"stem":481},{"title":485,"path":486,"stem":487,"children":488},"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",[489],{"title":485,"path":486,"stem":487},{"title":491,"path":492,"stem":493,"children":494},"HTTPException vs Custom Exception Classes in FastAPI","\u002Fcore-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Fhttpexception-vs-custom-exception-classes","core-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Fhttpexception-vs-custom-exception-classes\u002Findex",[495],{"title":491,"path":492,"stem":493},{"title":497,"path":498,"stem":499,"children":500},"Middleware Implementation in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fmiddleware-implementation","core-architecture-routing-patterns\u002Fmiddleware-implementation\u002Findex",[501,502,508,514,520],{"title":497,"path":498,"stem":499},{"title":503,"path":504,"stem":505,"children":506},"CORS Middleware Configuration in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fcors-middleware-configuration","core-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fcors-middleware-configuration\u002Findex",[507],{"title":503,"path":504,"stem":505},{"title":509,"path":510,"stem":511,"children":512},"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",[513],{"title":509,"path":510,"stem":511},{"title":515,"path":516,"stem":517,"children":518},"Middleware Execution Order in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fmiddleware-execution-order","core-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fmiddleware-execution-order\u002Findex",[519],{"title":515,"path":516,"stem":517},{"title":521,"path":522,"stem":523,"children":524},"Middleware vs Dependencies: When to Use Which","\u002Fcore-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fmiddleware-vs-dependencies-when-to-use-which","core-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fmiddleware-vs-dependencies-when-to-use-which\u002Findex",[525],{"title":521,"path":522,"stem":523},{"title":527,"path":528,"stem":529,"children":530},"Modular Router Organization in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fmodular-router-organization","core-architecture-routing-patterns\u002Fmodular-router-organization\u002Findex",[531,532,538,544,550],{"title":527,"path":528,"stem":529},{"title":533,"path":534,"stem":535,"children":536},"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",[537],{"title":533,"path":534,"stem":535},{"title":539,"path":540,"stem":541,"children":542},"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",[543],{"title":539,"path":540,"stem":541},{"title":545,"path":546,"stem":547,"children":548},"Router Tags and OpenAPI Grouping in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fmodular-router-organization\u002Frouter-tags-and-openapi-grouping","core-architecture-routing-patterns\u002Fmodular-router-organization\u002Frouter-tags-and-openapi-grouping\u002Findex",[549],{"title":545,"path":546,"stem":547},{"title":551,"path":552,"stem":553,"children":554},"Versioning APIs with FastAPI Routers","\u002Fcore-architecture-routing-patterns\u002Fmodular-router-organization\u002Fversioning-apis-with-routers","core-architecture-routing-patterns\u002Fmodular-router-organization\u002Fversioning-apis-with-routers\u002Findex",[555],{"title":551,"path":552,"stem":553},{"title":557,"path":558,"stem":559,"children":560},"The FastAPI Request\u002FResponse Lifecycle","\u002Fcore-architecture-routing-patterns\u002Frequest-response-lifecycle","core-architecture-routing-patterns\u002Frequest-response-lifecycle\u002Findex",[561,562,568,574],{"title":557,"path":558,"stem":559},{"title":563,"path":564,"stem":565,"children":566},"How a Request Flows Through FastAPI","\u002Fcore-architecture-routing-patterns\u002Frequest-response-lifecycle\u002Fhow-a-request-flows-through-fastapi","core-architecture-routing-patterns\u002Frequest-response-lifecycle\u002Fhow-a-request-flows-through-fastapi\u002Findex",[567],{"title":563,"path":564,"stem":565},{"title":569,"path":570,"stem":571,"children":572},"Response Model and Serialization Order","\u002Fcore-architecture-routing-patterns\u002Frequest-response-lifecycle\u002Fresponse-model-and-serialization-order","core-architecture-routing-patterns\u002Frequest-response-lifecycle\u002Fresponse-model-and-serialization-order\u002Findex",[573],{"title":569,"path":570,"stem":571},{"title":575,"path":576,"stem":577,"children":578},"Streaming and File Responses in FastAPI","\u002Fcore-architecture-routing-patterns\u002Frequest-response-lifecycle\u002Fstreaming-and-file-responses","core-architecture-routing-patterns\u002Frequest-response-lifecycle\u002Fstreaming-and-file-responses\u002Findex",[579],{"title":575,"path":576,"stem":577},{"id":581,"title":515,"body":582,"dateModified":2504,"datePublished":2504,"description":2505,"extension":2506,"faq":2507,"howto":2518,"meta":2519,"navigation":920,"path":516,"seo":2531,"stem":517,"type":2532,"__hash__":2533},"content\u002Fcore-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fmiddleware-execution-order\u002Findex.md",{"type":583,"value":584,"toc":2489},"minimark",[585,589,596,640,649,654,669,672,795,799,821,824,833,846,850,857,1811,1816,1822,1854,1858,1864,1895,1899,1905,1920,1924,1930,1933,1968,1997,2075,2079,2089,2092,2152,2165,2173,2177,2180,2320,2325,2329,2342,2360,2368,2378,2382,2391,2400,2409,2418,2430,2439,2443,2485],[586,587,515],"h1",{"id":588},"middleware-execution-order-in-fastapi",[590,591,592],"p",{},[593,594,595],"strong",{},"Key takeaways:",[597,598,599,611,619,630,637],"ul",{},[600,601,602,606,607,610],"li",{},[603,604,605],"code",{},"add_middleware"," pushes onto the front of the stack: ",[593,608,609],{},"the last one added is the outermost"," and runs first inbound, last outbound.",[600,612,613,614,618],{},"Starlette's exception handling sits ",[615,616,617],"em",{},"inside"," your middleware stack, between it and the router.",[600,620,621,622,625,626,629],{},"An ",[603,623,624],{},"HTTPException"," raised in middleware is therefore ",[593,627,628],{},"not"," converted to your status code — it escapes as an unhandled error.",[600,631,632,633,636],{},"A middleware that returns without calling ",[603,634,635],{},"call_next"," skips everything inner, including your logging and metrics.",[600,638,639],{},"An exception in one middleware also skips the outbound half of every middleware inside it.",[590,641,642,643,648],{},"This guide sits under ",[644,645,647],"a",{"href":646},"\u002Fcore-architecture-routing-patterns\u002Fmiddleware-implementation\u002F","middleware implementation"," and pins down the ordering rules that decide whether your cross-cutting layers actually see what you think they see.",[650,651,653],"h2",{"id":652},"the-problem-this-solves","The Problem This Solves",[590,655,656,657,660,661,664,665,668],{},"You have four middlewares: CORS, request tracing, an authentication gate, and a timing layer. In staging, the timing layer stops recording certain requests. A ",[603,658,659],{},"403"," from the auth layer arrives at the browser as a CORS error. An ",[603,662,663],{},"HTTPException(429)"," raised in a rate-limit middleware surfaces to clients as ",[603,666,667],{},"500 Internal Server Error",".",[590,670,671],{},"All three are ordering symptoms, and all three are predictable once you know where each layer sits.",[673,674,675,791],"figure",{},[676,677,685,686,685,690,685,694,685,703,685,710,685,718,685,724,685,730,685,734,685,742,685,748,685,756,685,762,685,769,685,774,685,780,685,783,685,786],"svg",{"viewBox":678,"role":679,"ariaLabelledBy":680,"xmlns":683,"style":684},"0 0 720 340","img",[681,682],"ord-t","ord-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;height:auto;max-width:720px;margin:2rem 0","\n  ",[687,688,689],"title",{"id":681},"Middleware stack ordering with exception handling inside it",[691,692,693],"desc",{"id":682},"Nested layers from outside in: the last middleware added, then earlier ones, then Starlette exception handling, then the router and the endpoint. Requests travel inward and responses travel outward through the same layers in reverse.",[695,696],"rect",{"x":697,"y":698,"width":699,"height":700,"rx":701,"style":702},"30","24","660","234","10","fill:none;stroke:#00796B;stroke-width:1.8px",[704,705,709],"text",{"x":706,"y":707,"style":708},"52","46","text-anchor:start;fill:#00695C;font:700 12px sans-serif","added last: outermost",[695,711],{"x":712,"y":713,"width":714,"height":715,"rx":716,"style":717},"72","58","576","182","9","fill:none;stroke:currentColor;stroke-width:1.5px",[704,719,723],{"x":720,"y":721,"style":722},"94","80","text-anchor:start;fill:currentColor;font:600 12px sans-serif","added earlier",[695,725],{"x":726,"y":727,"width":728,"height":729,"rx":716,"style":717},"114","92","492","132",[704,731,733],{"x":732,"y":726,"style":722},"136","added first: innermost",[695,735],{"x":736,"y":737,"width":738,"height":739,"rx":740,"style":741},"156","126","408","82","8","fill:none;stroke:#00796B;stroke-width:1.6px",[704,743,747],{"x":744,"y":745,"style":746},"178","148","text-anchor:start;fill:#00695C;font:600 12px sans-serif","exception handlers",[695,749],{"x":750,"y":751,"width":752,"height":753,"rx":754,"style":755},"200","158","320","40","7","fill:#E0F2F1;stroke:#00796B;stroke-width:1.5px",[704,757,761],{"x":758,"y":759,"style":760},"360","183","text-anchor:middle;fill:#00695C;font:700 13px sans-serif","router and endpoint",[763,764],"line",{"x1":765,"y1":766,"x2":767,"y2":766,"style":768},"60","286","400","stroke:#00796B;stroke-width:1.6px",[770,771],"polygon",{"points":772,"style":773},"400,281 410,286 400,291","fill:#00796B",[704,775,779],{"x":776,"y":777,"style":778},"230","278","text-anchor:middle;fill:currentColor;font:600 12px sans-serif","request travels inward",[763,781],{"x1":699,"y1":782,"x2":752,"y2":782,"style":768},"316",[770,784],{"points":785,"style":773},"320,311 310,316 320,321",[704,787,790],{"x":788,"y":789,"style":778},"490","308","response travels outward",[792,793,794],"figcaption",{},"Exception handling is a layer inside your middleware, not around it. Anything raised outside that box is unhandled.",[650,796,798],{"id":797},"why-it-happens-how-the-stack-is-built","Why It Happens: How the Stack Is Built",[590,800,801,804,805,808,809,812,813,816,817,820],{},[603,802,803],{},"FastAPI.add_middleware"," inserts into ",[603,806,807],{},"user_middleware"," at index 0. When the application builds its ASGI chain at startup, it composes those in order, so the entry registered most recently ends up wrapping everything registered before it. Around the whole set, Starlette places ",[603,810,811],{},"ServerErrorMiddleware"," outermost as the last-resort ",[603,814,815],{},"500"," producer, and ",[603,818,819],{},"ExceptionMiddleware"," innermost, immediately around the router.",[590,822,823],{},"That gives a fixed sandwich:",[825,826,831],"pre",{"className":827,"code":829,"language":704,"meta":830},[828],"language-text","ServerErrorMiddleware          (Starlette, always outermost)\n  your middleware, last added\n    your middleware, added earlier\n      your middleware, added first\n        ExceptionMiddleware    (runs your @app.exception_handler functions)\n          Router → dependencies → endpoint\n","",[603,832,829],{"__ignoreMap":830},[590,834,835,836,839,840,842,843,845],{},"Every surprising behaviour on this page falls out of that diagram. Your ",[603,837,838],{},"@app.exception_handler(SomeError)"," functions live in ",[603,841,819],{},", which is ",[615,844,617],{}," everything you registered. So they can convert exceptions raised by routes and dependencies — but they are structurally incapable of catching anything raised by your own middleware, which happens further out.",[650,847,849],{"id":848},"proving-it-a-logged-ordering","Proving It: a Logged Ordering",[590,851,852,853,856],{},"This app registers five middlewares that append to a shared list, plus one that short-circuits and one that raises. A ",[603,854,855],{},"\u002Fevents"," endpoint reports what the previous request logged.",[825,858,862],{"className":859,"code":860,"language":861,"meta":830,"style":830},"language-python shiki shiki-themes github-light-high-contrast","\"\"\"Middleware stack ordering: last added is outermost, and where exception handlers sit.\"\"\"\nfrom fastapi import FastAPI, HTTPException, Request\nfrom fastapi.responses import JSONResponse\nfrom starlette.middleware.base import BaseHTTPMiddleware\n\napp = FastAPI()\n\nEVENTS: list[str] = []\n\n\nclass Labelled(BaseHTTPMiddleware):\n    def __init__(self, app, label: str):\n        super().__init__(app)\n        self.label = label\n\n    async def dispatch(self, request: Request, call_next):\n        EVENTS.append(f\"{self.label}: before call_next\")\n        response = await call_next(request)\n        EVENTS.append(f\"{self.label}: after call_next (status {response.status_code})\")\n        response.headers[f\"x-{self.label}\"] = \"seen\"\n        return response\n\n\nclass ShortCircuit(BaseHTTPMiddleware):\n    \"\"\"Returns without ever calling call_next for one path — everything inner is skipped.\"\"\"\n\n    async def dispatch(self, request: Request, call_next):\n        EVENTS.append(\"short_circuit: before call_next\")\n        if request.url.path == \"\u002Fblocked\":\n            EVENTS.append(\"short_circuit: returning early, inner stack never runs\")\n            return JSONResponse({\"blocked\": True}, status_code=403)\n        return await call_next(request)\n\n\nclass Raiser(BaseHTTPMiddleware):\n    \"\"\"Raises inside dispatch to show it is NOT routed to FastAPI's exception handlers.\"\"\"\n\n    async def dispatch(self, request: Request, call_next):\n        EVENTS.append(\"raiser: before call_next\")\n        if request.url.path == \"\u002Fmw-error\":\n            EVENTS.append(\"raiser: raising HTTPException(418) from middleware\")\n            raise HTTPException(status_code=418, detail=\"raised in middleware\")\n        response = await call_next(request)\n        EVENTS.append(\"raiser: after call_next\")\n        return response\n\n\nclass SafetyNet(BaseHTTPMiddleware):\n    \"\"\"Outermost. Resets the log and catches whatever escapes the rest of the stack.\"\"\"\n\n    async def dispatch(self, request: Request, call_next):\n        EVENTS.clear()\n        EVENTS.append(\"safety_net: before call_next\")\n        try:\n            response = await call_next(request)\n        except Exception as exc:\n            EVENTS.append(f\"safety_net: caught {type(exc).__name__} escaping the stack\")\n            return JSONResponse({\"caught\": type(exc).__name__}, status_code=500)\n        EVENTS.append(f\"safety_net: after call_next (status {response.status_code})\")\n        return response\n\n\nclass MyDomainError(Exception):\n    pass\n\n\n@app.exception_handler(MyDomainError)\nasync def domain_error_handler(request: Request, exc: MyDomainError):\n    EVENTS.append(\"exception handler: converting MyDomainError to 422\")\n    return JSONResponse({\"error\": \"domain\", \"detail\": str(exc)}, status_code=422)\n\n\n# Added innermost-first. The LAST one added ends up OUTERMOST.\napp.add_middleware(Labelled, label=\"inner\")\napp.add_middleware(Raiser)\napp.add_middleware(ShortCircuit)\napp.add_middleware(Labelled, label=\"outer\")\napp.add_middleware(SafetyNet)\n","python",[603,863,864,872,889,902,915,922,934,939,960,965,970,989,1005,1020,1034,1039,1055,1088,1102,1136,1164,1173,1178,1183,1197,1203,1208,1219,1231,1249,1262,1292,1301,1306,1311,1325,1331,1336,1347,1359,1373,1385,1414,1425,1437,1444,1449,1454,1468,1474,1479,1490,1498,1510,1518,1530,1545,1575,1603,1625,1632,1637,1642,1657,1663,1668,1673,1682,1696,1709,1746,1751,1756,1763,1779,1785,1791,1805],{"__ignoreMap":830},[865,866,868],"span",{"class":763,"line":867},1,[865,869,871],{"class":870},"sYEJz","\"\"\"Middleware stack ordering: last added is outermost, and where exception handlers sit.\"\"\"\n",[865,873,875,879,883,886],{"class":763,"line":874},2,[865,876,878],{"class":877},"sTJeM","from",[865,880,882],{"class":881},"sigWx"," fastapi ",[865,884,885],{"class":877},"import",[865,887,888],{"class":881}," FastAPI, HTTPException, Request\n",[865,890,892,894,897,899],{"class":763,"line":891},3,[865,893,878],{"class":877},[865,895,896],{"class":881}," fastapi.responses ",[865,898,885],{"class":877},[865,900,901],{"class":881}," JSONResponse\n",[865,903,905,907,910,912],{"class":763,"line":904},4,[865,906,878],{"class":877},[865,908,909],{"class":881}," starlette.middleware.base ",[865,911,885],{"class":877},[865,913,914],{"class":881}," BaseHTTPMiddleware\n",[865,916,918],{"class":763,"line":917},5,[865,919,921],{"emptyLinePlaceholder":920},true,"\n",[865,923,925,928,931],{"class":763,"line":924},6,[865,926,927],{"class":881},"app ",[865,929,930],{"class":877},"=",[865,932,933],{"class":881}," FastAPI()\n",[865,935,937],{"class":763,"line":936},7,[865,938,921],{"emptyLinePlaceholder":920},[865,940,942,946,949,952,955,957],{"class":763,"line":941},8,[865,943,945],{"class":944},"sacAq","EVENTS",[865,947,948],{"class":881},": list[",[865,950,951],{"class":944},"str",[865,953,954],{"class":881},"] ",[865,956,930],{"class":877},[865,958,959],{"class":881}," []\n",[865,961,963],{"class":763,"line":962},9,[865,964,921],{"emptyLinePlaceholder":920},[865,966,968],{"class":763,"line":967},10,[865,969,921],{"emptyLinePlaceholder":920},[865,971,973,976,980,983,986],{"class":763,"line":972},11,[865,974,975],{"class":877},"class",[865,977,979],{"class":978},"sV4o_"," Labelled",[865,981,982],{"class":881},"(",[865,984,985],{"class":944},"BaseHTTPMiddleware",[865,987,988],{"class":881},"):\n",[865,990,992,995,998,1001,1003],{"class":763,"line":991},12,[865,993,994],{"class":877},"    def",[865,996,997],{"class":944}," __init__",[865,999,1000],{"class":881},"(self, app, label: ",[865,1002,951],{"class":944},[865,1004,988],{"class":881},[865,1006,1008,1011,1014,1017],{"class":763,"line":1007},13,[865,1009,1010],{"class":944},"        super",[865,1012,1013],{"class":881},"().",[865,1015,1016],{"class":944},"__init__",[865,1018,1019],{"class":881},"(app)\n",[865,1021,1023,1026,1029,1031],{"class":763,"line":1022},14,[865,1024,1025],{"class":944},"        self",[865,1027,1028],{"class":881},".label ",[865,1030,930],{"class":877},[865,1032,1033],{"class":881}," label\n",[865,1035,1037],{"class":763,"line":1036},15,[865,1038,921],{"emptyLinePlaceholder":920},[865,1040,1042,1045,1048,1052],{"class":763,"line":1041},16,[865,1043,1044],{"class":877},"    async",[865,1046,1047],{"class":877}," def",[865,1049,1051],{"class":1050},"s3dhs"," dispatch",[865,1053,1054],{"class":881},"(self, request: Request, call_next):\n",[865,1056,1058,1061,1064,1067,1070,1073,1076,1079,1082,1085],{"class":763,"line":1057},17,[865,1059,1060],{"class":944},"        EVENTS",[865,1062,1063],{"class":881},".append(",[865,1065,1066],{"class":877},"f",[865,1068,1069],{"class":870},"\"",[865,1071,1072],{"class":877},"{",[865,1074,1075],{"class":944},"self",[865,1077,1078],{"class":881},".label",[865,1080,1081],{"class":877},"}",[865,1083,1084],{"class":870},": before call_next\"",[865,1086,1087],{"class":881},")\n",[865,1089,1091,1094,1096,1099],{"class":763,"line":1090},18,[865,1092,1093],{"class":881},"        response ",[865,1095,930],{"class":877},[865,1097,1098],{"class":877}," await",[865,1100,1101],{"class":881}," call_next(request)\n",[865,1103,1105,1107,1109,1111,1113,1115,1117,1119,1121,1124,1126,1129,1131,1134],{"class":763,"line":1104},19,[865,1106,1060],{"class":944},[865,1108,1063],{"class":881},[865,1110,1066],{"class":877},[865,1112,1069],{"class":870},[865,1114,1072],{"class":877},[865,1116,1075],{"class":944},[865,1118,1078],{"class":881},[865,1120,1081],{"class":877},[865,1122,1123],{"class":870},": after call_next (status ",[865,1125,1072],{"class":877},[865,1127,1128],{"class":881},"response.status_code",[865,1130,1081],{"class":877},[865,1132,1133],{"class":870},")\"",[865,1135,1087],{"class":881},[865,1137,1139,1142,1144,1147,1149,1151,1153,1155,1157,1159,1161],{"class":763,"line":1138},20,[865,1140,1141],{"class":881},"        response.headers[",[865,1143,1066],{"class":877},[865,1145,1146],{"class":870},"\"x-",[865,1148,1072],{"class":877},[865,1150,1075],{"class":944},[865,1152,1078],{"class":881},[865,1154,1081],{"class":877},[865,1156,1069],{"class":870},[865,1158,954],{"class":881},[865,1160,930],{"class":877},[865,1162,1163],{"class":870}," \"seen\"\n",[865,1165,1167,1170],{"class":763,"line":1166},21,[865,1168,1169],{"class":877},"        return",[865,1171,1172],{"class":881}," response\n",[865,1174,1176],{"class":763,"line":1175},22,[865,1177,921],{"emptyLinePlaceholder":920},[865,1179,1181],{"class":763,"line":1180},23,[865,1182,921],{"emptyLinePlaceholder":920},[865,1184,1186,1188,1191,1193,1195],{"class":763,"line":1185},24,[865,1187,975],{"class":877},[865,1189,1190],{"class":978}," ShortCircuit",[865,1192,982],{"class":881},[865,1194,985],{"class":944},[865,1196,988],{"class":881},[865,1198,1200],{"class":763,"line":1199},25,[865,1201,1202],{"class":870},"    \"\"\"Returns without ever calling call_next for one path — everything inner is skipped.\"\"\"\n",[865,1204,1206],{"class":763,"line":1205},26,[865,1207,921],{"emptyLinePlaceholder":920},[865,1209,1211,1213,1215,1217],{"class":763,"line":1210},27,[865,1212,1044],{"class":877},[865,1214,1047],{"class":877},[865,1216,1051],{"class":1050},[865,1218,1054],{"class":881},[865,1220,1222,1224,1226,1229],{"class":763,"line":1221},28,[865,1223,1060],{"class":944},[865,1225,1063],{"class":881},[865,1227,1228],{"class":870},"\"short_circuit: before call_next\"",[865,1230,1087],{"class":881},[865,1232,1234,1237,1240,1243,1246],{"class":763,"line":1233},29,[865,1235,1236],{"class":877},"        if",[865,1238,1239],{"class":881}," request.url.path ",[865,1241,1242],{"class":877},"==",[865,1244,1245],{"class":870}," \"\u002Fblocked\"",[865,1247,1248],{"class":881},":\n",[865,1250,1252,1255,1257,1260],{"class":763,"line":1251},30,[865,1253,1254],{"class":944},"            EVENTS",[865,1256,1063],{"class":881},[865,1258,1259],{"class":870},"\"short_circuit: returning early, inner stack never runs\"",[865,1261,1087],{"class":881},[865,1263,1265,1268,1271,1274,1277,1280,1283,1286,1288,1290],{"class":763,"line":1264},31,[865,1266,1267],{"class":877},"            return",[865,1269,1270],{"class":881}," JSONResponse({",[865,1272,1273],{"class":870},"\"blocked\"",[865,1275,1276],{"class":881},": ",[865,1278,1279],{"class":944},"True",[865,1281,1282],{"class":881},"}, ",[865,1284,1285],{"class":978},"status_code",[865,1287,930],{"class":877},[865,1289,659],{"class":944},[865,1291,1087],{"class":881},[865,1293,1295,1297,1299],{"class":763,"line":1294},32,[865,1296,1169],{"class":877},[865,1298,1098],{"class":877},[865,1300,1101],{"class":881},[865,1302,1304],{"class":763,"line":1303},33,[865,1305,921],{"emptyLinePlaceholder":920},[865,1307,1309],{"class":763,"line":1308},34,[865,1310,921],{"emptyLinePlaceholder":920},[865,1312,1314,1316,1319,1321,1323],{"class":763,"line":1313},35,[865,1315,975],{"class":877},[865,1317,1318],{"class":978}," Raiser",[865,1320,982],{"class":881},[865,1322,985],{"class":944},[865,1324,988],{"class":881},[865,1326,1328],{"class":763,"line":1327},36,[865,1329,1330],{"class":870},"    \"\"\"Raises inside dispatch to show it is NOT routed to FastAPI's exception handlers.\"\"\"\n",[865,1332,1334],{"class":763,"line":1333},37,[865,1335,921],{"emptyLinePlaceholder":920},[865,1337,1339,1341,1343,1345],{"class":763,"line":1338},38,[865,1340,1044],{"class":877},[865,1342,1047],{"class":877},[865,1344,1051],{"class":1050},[865,1346,1054],{"class":881},[865,1348,1350,1352,1354,1357],{"class":763,"line":1349},39,[865,1351,1060],{"class":944},[865,1353,1063],{"class":881},[865,1355,1356],{"class":870},"\"raiser: before call_next\"",[865,1358,1087],{"class":881},[865,1360,1362,1364,1366,1368,1371],{"class":763,"line":1361},40,[865,1363,1236],{"class":877},[865,1365,1239],{"class":881},[865,1367,1242],{"class":877},[865,1369,1370],{"class":870}," \"\u002Fmw-error\"",[865,1372,1248],{"class":881},[865,1374,1376,1378,1380,1383],{"class":763,"line":1375},41,[865,1377,1254],{"class":944},[865,1379,1063],{"class":881},[865,1381,1382],{"class":870},"\"raiser: raising HTTPException(418) from middleware\"",[865,1384,1087],{"class":881},[865,1386,1388,1391,1394,1396,1398,1401,1404,1407,1409,1412],{"class":763,"line":1387},42,[865,1389,1390],{"class":877},"            raise",[865,1392,1393],{"class":881}," HTTPException(",[865,1395,1285],{"class":978},[865,1397,930],{"class":877},[865,1399,1400],{"class":944},"418",[865,1402,1403],{"class":881},", ",[865,1405,1406],{"class":978},"detail",[865,1408,930],{"class":877},[865,1410,1411],{"class":870},"\"raised in middleware\"",[865,1413,1087],{"class":881},[865,1415,1417,1419,1421,1423],{"class":763,"line":1416},43,[865,1418,1093],{"class":881},[865,1420,930],{"class":877},[865,1422,1098],{"class":877},[865,1424,1101],{"class":881},[865,1426,1428,1430,1432,1435],{"class":763,"line":1427},44,[865,1429,1060],{"class":944},[865,1431,1063],{"class":881},[865,1433,1434],{"class":870},"\"raiser: after call_next\"",[865,1436,1087],{"class":881},[865,1438,1440,1442],{"class":763,"line":1439},45,[865,1441,1169],{"class":877},[865,1443,1172],{"class":881},[865,1445,1447],{"class":763,"line":1446},46,[865,1448,921],{"emptyLinePlaceholder":920},[865,1450,1452],{"class":763,"line":1451},47,[865,1453,921],{"emptyLinePlaceholder":920},[865,1455,1457,1459,1462,1464,1466],{"class":763,"line":1456},48,[865,1458,975],{"class":877},[865,1460,1461],{"class":978}," SafetyNet",[865,1463,982],{"class":881},[865,1465,985],{"class":944},[865,1467,988],{"class":881},[865,1469,1471],{"class":763,"line":1470},49,[865,1472,1473],{"class":870},"    \"\"\"Outermost. Resets the log and catches whatever escapes the rest of the stack.\"\"\"\n",[865,1475,1477],{"class":763,"line":1476},50,[865,1478,921],{"emptyLinePlaceholder":920},[865,1480,1482,1484,1486,1488],{"class":763,"line":1481},51,[865,1483,1044],{"class":877},[865,1485,1047],{"class":877},[865,1487,1051],{"class":1050},[865,1489,1054],{"class":881},[865,1491,1493,1495],{"class":763,"line":1492},52,[865,1494,1060],{"class":944},[865,1496,1497],{"class":881},".clear()\n",[865,1499,1501,1503,1505,1508],{"class":763,"line":1500},53,[865,1502,1060],{"class":944},[865,1504,1063],{"class":881},[865,1506,1507],{"class":870},"\"safety_net: before call_next\"",[865,1509,1087],{"class":881},[865,1511,1513,1516],{"class":763,"line":1512},54,[865,1514,1515],{"class":877},"        try",[865,1517,1248],{"class":881},[865,1519,1521,1524,1526,1528],{"class":763,"line":1520},55,[865,1522,1523],{"class":881},"            response ",[865,1525,930],{"class":877},[865,1527,1098],{"class":877},[865,1529,1101],{"class":881},[865,1531,1533,1536,1539,1542],{"class":763,"line":1532},56,[865,1534,1535],{"class":877},"        except",[865,1537,1538],{"class":944}," Exception",[865,1540,1541],{"class":877}," as",[865,1543,1544],{"class":881}," exc:\n",[865,1546,1548,1550,1552,1554,1557,1559,1562,1565,1568,1570,1573],{"class":763,"line":1547},57,[865,1549,1254],{"class":944},[865,1551,1063],{"class":881},[865,1553,1066],{"class":877},[865,1555,1556],{"class":870},"\"safety_net: caught ",[865,1558,1072],{"class":877},[865,1560,1561],{"class":944},"type",[865,1563,1564],{"class":881},"(exc).",[865,1566,1567],{"class":944},"__name__",[865,1569,1081],{"class":877},[865,1571,1572],{"class":870}," escaping the stack\"",[865,1574,1087],{"class":881},[865,1576,1578,1580,1582,1585,1587,1589,1591,1593,1595,1597,1599,1601],{"class":763,"line":1577},58,[865,1579,1267],{"class":877},[865,1581,1270],{"class":881},[865,1583,1584],{"class":870},"\"caught\"",[865,1586,1276],{"class":881},[865,1588,1561],{"class":944},[865,1590,1564],{"class":881},[865,1592,1567],{"class":944},[865,1594,1282],{"class":881},[865,1596,1285],{"class":978},[865,1598,930],{"class":877},[865,1600,815],{"class":944},[865,1602,1087],{"class":881},[865,1604,1606,1608,1610,1612,1615,1617,1619,1621,1623],{"class":763,"line":1605},59,[865,1607,1060],{"class":944},[865,1609,1063],{"class":881},[865,1611,1066],{"class":877},[865,1613,1614],{"class":870},"\"safety_net: after call_next (status ",[865,1616,1072],{"class":877},[865,1618,1128],{"class":881},[865,1620,1081],{"class":877},[865,1622,1133],{"class":870},[865,1624,1087],{"class":881},[865,1626,1628,1630],{"class":763,"line":1627},60,[865,1629,1169],{"class":877},[865,1631,1172],{"class":881},[865,1633,1635],{"class":763,"line":1634},61,[865,1636,921],{"emptyLinePlaceholder":920},[865,1638,1640],{"class":763,"line":1639},62,[865,1641,921],{"emptyLinePlaceholder":920},[865,1643,1645,1647,1650,1652,1655],{"class":763,"line":1644},63,[865,1646,975],{"class":877},[865,1648,1649],{"class":978}," MyDomainError",[865,1651,982],{"class":881},[865,1653,1654],{"class":944},"Exception",[865,1656,988],{"class":881},[865,1658,1660],{"class":763,"line":1659},64,[865,1661,1662],{"class":877},"    pass\n",[865,1664,1666],{"class":763,"line":1665},65,[865,1667,921],{"emptyLinePlaceholder":920},[865,1669,1671],{"class":763,"line":1670},66,[865,1672,921],{"emptyLinePlaceholder":920},[865,1674,1676,1679],{"class":763,"line":1675},67,[865,1677,1678],{"class":1050},"@app.exception_handler",[865,1680,1681],{"class":881},"(MyDomainError)\n",[865,1683,1685,1688,1690,1693],{"class":763,"line":1684},68,[865,1686,1687],{"class":877},"async",[865,1689,1047],{"class":877},[865,1691,1692],{"class":1050}," domain_error_handler",[865,1694,1695],{"class":881},"(request: Request, exc: MyDomainError):\n",[865,1697,1699,1702,1704,1707],{"class":763,"line":1698},69,[865,1700,1701],{"class":944},"    EVENTS",[865,1703,1063],{"class":881},[865,1705,1706],{"class":870},"\"exception handler: converting MyDomainError to 422\"",[865,1708,1087],{"class":881},[865,1710,1712,1715,1717,1720,1722,1725,1727,1730,1732,1734,1737,1739,1741,1744],{"class":763,"line":1711},70,[865,1713,1714],{"class":877},"    return",[865,1716,1270],{"class":881},[865,1718,1719],{"class":870},"\"error\"",[865,1721,1276],{"class":881},[865,1723,1724],{"class":870},"\"domain\"",[865,1726,1403],{"class":881},[865,1728,1729],{"class":870},"\"detail\"",[865,1731,1276],{"class":881},[865,1733,951],{"class":944},[865,1735,1736],{"class":881},"(exc)}, ",[865,1738,1285],{"class":978},[865,1740,930],{"class":877},[865,1742,1743],{"class":944},"422",[865,1745,1087],{"class":881},[865,1747,1749],{"class":763,"line":1748},71,[865,1750,921],{"emptyLinePlaceholder":920},[865,1752,1754],{"class":763,"line":1753},72,[865,1755,921],{"emptyLinePlaceholder":920},[865,1757,1759],{"class":763,"line":1758},73,[865,1760,1762],{"class":1761},"sFeEa","# Added innermost-first. The LAST one added ends up OUTERMOST.\n",[865,1764,1766,1769,1772,1774,1777],{"class":763,"line":1765},74,[865,1767,1768],{"class":881},"app.add_middleware(Labelled, ",[865,1770,1771],{"class":978},"label",[865,1773,930],{"class":877},[865,1775,1776],{"class":870},"\"inner\"",[865,1778,1087],{"class":881},[865,1780,1782],{"class":763,"line":1781},75,[865,1783,1784],{"class":881},"app.add_middleware(Raiser)\n",[865,1786,1788],{"class":763,"line":1787},76,[865,1789,1790],{"class":881},"app.add_middleware(ShortCircuit)\n",[865,1792,1794,1796,1798,1800,1803],{"class":763,"line":1793},77,[865,1795,1768],{"class":881},[865,1797,1771],{"class":978},[865,1799,930],{"class":877},[865,1801,1802],{"class":870},"\"outer\"",[865,1804,1087],{"class":881},[865,1806,1808],{"class":763,"line":1807},78,[865,1809,1810],{"class":881},"app.add_middleware(SafetyNet)\n",[1812,1813,1815],"h3",{"id":1814},"the-ordering-rule-confirmed","The ordering rule, confirmed",[825,1817,1820],{"className":1818,"code":1819,"language":704,"meta":830},[828],"$ GET \u002Fevents\n200 OK\n{\n  \"events\": [\n    \"safety_net: before call_next\",\n    \"outer: before call_next\",\n    \"short_circuit: before call_next\",\n    \"raiser: before call_next\",\n    \"inner: before call_next\",\n    \"endpoint: \u002Fok body ran\",\n    \"inner: after call_next (status 200)\",\n    \"raiser: after call_next\",\n    \"outer: after call_next (status 200)\",\n    \"safety_net: after call_next (status 200)\"\n  ]\n}\n",[603,1821,1819],{"__ignoreMap":830},[590,1823,1824,1825,1403,1828,1403,1831,1403,1834,1403,1837,1840,1841,1844,1845,1847,1848,1850,1851,1853],{},"Registration order was ",[603,1826,1827],{},"inner",[603,1829,1830],{},"Raiser",[603,1832,1833],{},"ShortCircuit",[603,1835,1836],{},"outer",[603,1838,1839],{},"SafetyNet",". Execution order inbound is the exact reverse: ",[603,1842,1843],{},"safety_net"," first, ",[603,1846,1827],{}," last. Outbound it reverses again, so ",[603,1849,1827],{}," sees the response first and ",[603,1852,1843],{}," sees it last — which is why an outermost timing middleware measures everything inside it, and an outermost header-setting middleware wins any conflict over the same header name.",[1812,1855,1857],{"id":1856},"exception-handlers-run-inside-the-stack","Exception handlers run inside the stack",[825,1859,1862],{"className":1860,"code":1861,"language":704,"meta":830},[828],"$ GET \u002Fdomain-error\n422 Unprocessable Entity\n{\n  \"error\": \"domain\",\n  \"detail\": \"inventory is negative\"\n}\n\n$ GET \u002Fevents\n200 OK\n{\n  \"events\": [\n    \"safety_net: before call_next\",\n    \"outer: before call_next\",\n    \"short_circuit: before call_next\",\n    \"raiser: before call_next\",\n    \"inner: before call_next\",\n    \"endpoint: raising MyDomainError\",\n    \"exception handler: converting MyDomainError to 422\",\n    \"inner: after call_next (status 422)\",\n    \"raiser: after call_next\",\n    \"outer: after call_next (status 422)\",\n    \"safety_net: after call_next (status 422)\"\n  ]\n}\n",[603,1863,1861],{"__ignoreMap":830},[590,1865,1866,1867,1870,1871,1873,1874,1876,1877,1880,1881,1884,1885,1889,1890,1894],{},"The handler ran ",[615,1868,1869],{},"between"," the endpoint and the innermost middleware. By the time ",[603,1872,1827],{}," resumes, the exception has already become a ",[603,1875,1743],{}," response object — every middleware sees a normal response with a normal status code, and none of them needs a ",[603,1878,1879],{},"try","\u002F",[603,1882,1883],{},"except",". This is why the ",[644,1886,1888],{"href":1887},"\u002Fcore-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Fcustomising-validation-error-responses\u002F","validation error envelope"," and other ",[644,1891,1893],{"href":1892},"\u002Fcore-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Fglobal-exception-handlers-for-consistent-api-responses\u002F","global exception handlers"," compose cleanly with middleware: they produce responses, not exceptions.",[1812,1896,1898],{"id":1897},"short-circuiting-skips-the-inner-stack-entirely","Short-circuiting skips the inner stack entirely",[825,1900,1903],{"className":1901,"code":1902,"language":704,"meta":830},[828],"$ GET \u002Fblocked\n403 Forbidden\n{\n  \"blocked\": true\n}\n\n$ GET \u002Fevents\n200 OK\n{\n  \"events\": [\n    \"safety_net: before call_next\",\n    \"outer: before call_next\",\n    \"short_circuit: before call_next\",\n    \"short_circuit: returning early, inner stack never runs\",\n    \"outer: after call_next (status 403)\",\n    \"safety_net: after call_next (status 403)\"\n  ]\n}\n",[603,1904,1902],{"__ignoreMap":830},[590,1906,1907,1910,1911,1913,1914,1916,1917,1919],{},[603,1908,1909],{},"raiser"," and ",[603,1912,1827],{}," never appear — not on the way in, not on the way out. The endpoint never ran. Anything you rely on inside that boundary is silently absent: if ",[603,1915,1827],{}," were your access logger, every blocked request would be missing from the log while still appearing in ",[603,1918,1836],{},"'s metrics. This is the concrete answer to \"why is this request missing from my logs\".",[1812,1921,1923],{"id":1922},"an-exception-in-middleware-bypasses-the-middleware-inside-it","An exception in middleware bypasses the middleware inside it",[825,1925,1928],{"className":1926,"code":1927,"language":704,"meta":830},[828],"$ GET \u002Fmw-error\n500 Internal Server Error\n{\n  \"caught\": \"HTTPException\"\n}\n\n$ GET \u002Fevents\n200 OK\n{\n  \"events\": [\n    \"safety_net: before call_next\",\n    \"outer: before call_next\",\n    \"short_circuit: before call_next\",\n    \"raiser: before call_next\",\n    \"raiser: raising HTTPException(418) from middleware\",\n    \"safety_net: caught HTTPException escaping the stack\"\n  ]\n}\n",[603,1929,1927],{"__ignoreMap":830},[590,1931,1932],{},"Two results in one transcript, and both are the ones people get wrong.",[590,1934,1935,1942,1943,1945,1946,1948,1949,1951,1952,1954,1955,1957,1958,1960,1961,1963,1964,1967],{},[593,1936,1937,1938,1941],{},"The ",[603,1939,1940],{},"HTTPException(418)"," did not become a 418."," It was raised in ",[603,1944,1909],{},", which lives outside ",[603,1947,819],{},", so nothing translated it. It propagated outward as an ordinary Python exception and was caught by ",[603,1950,1843],{}," — and had ",[603,1953,1843],{}," not existed, Starlette's ",[603,1956,811],{}," would have turned it into a bare ",[603,1959,815],{},". Raising ",[603,1962,624],{}," in middleware is a mistake; return a ",[603,1965,1966],{},"JSONResponse"," directly instead.",[590,1969,1970,1978,1979,1982,1983,1985,1986,1989,1990,1992,1993,1996],{},[593,1971,1972,1910,1974,1977],{},[603,1973,1836],{},[603,1975,1976],{},"short_circuit"," never ran their outbound halves."," Their ",[603,1980,1981],{},"after call_next"," lines are absent, because for them ",[603,1984,635],{}," raised rather than returned. Any middleware that sets a response header, records a duration, or resets a ",[603,1987,1988],{},"contextvar"," after ",[603,1991,635],{}," is skipped for that request. A ",[603,1994,1995],{},"finally"," block is the fix:",[825,1998,2000],{"className":859,"code":1999,"language":861,"meta":830,"style":830},"class TimingMiddleware(BaseHTTPMiddleware):\n    async def dispatch(self, request: Request, call_next):\n        started = time.perf_counter()\n        try:\n            return await call_next(request)\n        finally:\n            # Runs even when an inner layer raises, so the metric is never lost.\n            OBSERVED.labels(request.url.path).observe(time.perf_counter() - started)\n",[603,2001,2002,2015,2025,2035,2041,2049,2056,2061],{"__ignoreMap":830},[865,2003,2004,2006,2009,2011,2013],{"class":763,"line":867},[865,2005,975],{"class":877},[865,2007,2008],{"class":978}," TimingMiddleware",[865,2010,982],{"class":881},[865,2012,985],{"class":944},[865,2014,988],{"class":881},[865,2016,2017,2019,2021,2023],{"class":763,"line":874},[865,2018,1044],{"class":877},[865,2020,1047],{"class":877},[865,2022,1051],{"class":1050},[865,2024,1054],{"class":881},[865,2026,2027,2030,2032],{"class":763,"line":891},[865,2028,2029],{"class":881},"        started ",[865,2031,930],{"class":877},[865,2033,2034],{"class":881}," time.perf_counter()\n",[865,2036,2037,2039],{"class":763,"line":904},[865,2038,1515],{"class":877},[865,2040,1248],{"class":881},[865,2042,2043,2045,2047],{"class":763,"line":917},[865,2044,1267],{"class":877},[865,2046,1098],{"class":877},[865,2048,1101],{"class":881},[865,2050,2051,2054],{"class":763,"line":924},[865,2052,2053],{"class":877},"        finally",[865,2055,1248],{"class":881},[865,2057,2058],{"class":763,"line":936},[865,2059,2060],{"class":1761},"            # Runs even when an inner layer raises, so the metric is never lost.\n",[865,2062,2063,2066,2069,2072],{"class":763,"line":941},[865,2064,2065],{"class":944},"            OBSERVED",[865,2067,2068],{"class":881},".labels(request.url.path).observe(time.perf_counter() ",[865,2070,2071],{"class":877},"-",[865,2073,2074],{"class":881}," started)\n",[650,2076,2078],{"id":2077},"choosing-an-order","Choosing an Order",[590,2080,2081,2082,2085,2086,2088],{},"The rule of thumb: ",[593,2083,2084],{},"register in the order you want them to run innermost-first",", or read your ",[603,2087,605],{}," calls bottom-up.",[590,2090,2091],{},"A stack that works for most production applications, written in registration order:",[825,2093,2095],{"className":859,"code":2094,"language":861,"meta":830,"style":830},"app.add_middleware(TimingMiddleware)          # innermost: measures handler work only\napp.add_middleware(AuthGateMiddleware)        # can short-circuit before the handler\napp.add_middleware(TracingMiddleware)         # must wrap auth so rejected requests get an ID\napp.add_middleware(GZipMiddleware, minimum_size=1000)\napp.add_middleware(CORSMiddleware, allow_origins=settings.cors_origins)   # outermost\n",[603,2096,2097,2105,2113,2121,2136],{"__ignoreMap":830},[865,2098,2099,2102],{"class":763,"line":867},[865,2100,2101],{"class":881},"app.add_middleware(TimingMiddleware)          ",[865,2103,2104],{"class":1761},"# innermost: measures handler work only\n",[865,2106,2107,2110],{"class":763,"line":874},[865,2108,2109],{"class":881},"app.add_middleware(AuthGateMiddleware)        ",[865,2111,2112],{"class":1761},"# can short-circuit before the handler\n",[865,2114,2115,2118],{"class":763,"line":891},[865,2116,2117],{"class":881},"app.add_middleware(TracingMiddleware)         ",[865,2119,2120],{"class":1761},"# must wrap auth so rejected requests get an ID\n",[865,2122,2123,2126,2129,2131,2134],{"class":763,"line":904},[865,2124,2125],{"class":881},"app.add_middleware(GZipMiddleware, ",[865,2127,2128],{"class":978},"minimum_size",[865,2130,930],{"class":877},[865,2132,2133],{"class":944},"1000",[865,2135,1087],{"class":881},[865,2137,2138,2141,2144,2146,2149],{"class":763,"line":917},[865,2139,2140],{"class":881},"app.add_middleware(CORSMiddleware, ",[865,2142,2143],{"class":978},"allow_origins",[865,2145,930],{"class":877},[865,2147,2148],{"class":881},"settings.cors_origins)   ",[865,2150,2151],{"class":1761},"# outermost\n",[590,2153,2154,2157,2158,2160,2161,668],{},[603,2155,2156],{},"CORSMiddleware"," last is not a style preference. If an inner layer returns a ",[603,2159,659],{}," or raises, that response only carries CORS headers when CORS is outside it — otherwise the browser reports a CORS failure and hides the real status from your frontend, exactly the confusion described in ",[644,2162,2164],{"href":2163},"\u002Fcore-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fcors-middleware-configuration\u002F","CORS middleware configuration",[590,2166,2167,2168,2172],{},"Tracing outside auth follows the same logic: a rejected request is the one you most want a correlation ID for, and ",[644,2169,2171],{"href":2170},"\u002Fcore-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fimplementing-custom-middleware-for-request-tracing\u002F","request-tracing middleware"," placed inside an auth gate never sees it.",[650,2174,2176],{"id":2175},"verification","Verification",[590,2178,2179],{},"Assert the order rather than trusting it, using a header each layer stamps:",[825,2181,2183],{"className":859,"code":2182,"language":861,"meta":830,"style":830},"def test_stack_order(client):\n    r = client.get(\"\u002Fhealth\")\n    # Every layer ran outbound; a short-circuit or an exception would drop one.\n    assert {\"x-outer\", \"x-inner\"} \u003C= set(r.headers)\n\n\ndef test_error_response_still_has_cors(client):\n    r = client.get(\"\u002Falways-500\", headers={\"Origin\": \"https:\u002F\u002Fapp.example.com\"})\n    assert r.status_code == 500\n    assert \"access-control-allow-origin\" in r.headers   # CORS is outside the failure\n",[603,2184,2185,2196,2211,2216,2244,2248,2252,2261,2292,2304],{"__ignoreMap":830},[865,2186,2187,2190,2193],{"class":763,"line":867},[865,2188,2189],{"class":877},"def",[865,2191,2192],{"class":1050}," test_stack_order",[865,2194,2195],{"class":881},"(client):\n",[865,2197,2198,2201,2203,2206,2209],{"class":763,"line":874},[865,2199,2200],{"class":881},"    r ",[865,2202,930],{"class":877},[865,2204,2205],{"class":881}," client.get(",[865,2207,2208],{"class":870},"\"\u002Fhealth\"",[865,2210,1087],{"class":881},[865,2212,2213],{"class":763,"line":891},[865,2214,2215],{"class":1761},"    # Every layer ran outbound; a short-circuit or an exception would drop one.\n",[865,2217,2218,2221,2224,2227,2229,2232,2235,2238,2241],{"class":763,"line":904},[865,2219,2220],{"class":877},"    assert",[865,2222,2223],{"class":881}," {",[865,2225,2226],{"class":870},"\"x-outer\"",[865,2228,1403],{"class":881},[865,2230,2231],{"class":870},"\"x-inner\"",[865,2233,2234],{"class":881},"} ",[865,2236,2237],{"class":877},"\u003C=",[865,2239,2240],{"class":944}," set",[865,2242,2243],{"class":881},"(r.headers)\n",[865,2245,2246],{"class":763,"line":917},[865,2247,921],{"emptyLinePlaceholder":920},[865,2249,2250],{"class":763,"line":924},[865,2251,921],{"emptyLinePlaceholder":920},[865,2253,2254,2256,2259],{"class":763,"line":936},[865,2255,2189],{"class":877},[865,2257,2258],{"class":1050}," test_error_response_still_has_cors",[865,2260,2195],{"class":881},[865,2262,2263,2265,2267,2269,2272,2274,2277,2279,2281,2284,2286,2289],{"class":763,"line":941},[865,2264,2200],{"class":881},[865,2266,930],{"class":877},[865,2268,2205],{"class":881},[865,2270,2271],{"class":870},"\"\u002Falways-500\"",[865,2273,1403],{"class":881},[865,2275,2276],{"class":978},"headers",[865,2278,930],{"class":877},[865,2280,1072],{"class":881},[865,2282,2283],{"class":870},"\"Origin\"",[865,2285,1276],{"class":881},[865,2287,2288],{"class":870},"\"https:\u002F\u002Fapp.example.com\"",[865,2290,2291],{"class":881},"})\n",[865,2293,2294,2296,2299,2301],{"class":763,"line":962},[865,2295,2220],{"class":877},[865,2297,2298],{"class":881}," r.status_code ",[865,2300,1242],{"class":877},[865,2302,2303],{"class":944}," 500\n",[865,2305,2306,2308,2311,2314,2317],{"class":763,"line":967},[865,2307,2220],{"class":877},[865,2309,2310],{"class":870}," \"access-control-allow-origin\"",[865,2312,2313],{"class":877}," in",[865,2315,2316],{"class":881}," r.headers   ",[865,2318,2319],{"class":1761},"# CORS is outside the failure\n",[590,2321,2322,2323,668],{},"The second test is the one that catches real incidents, because it fails exactly when a browser would have shown a misleading CORS error instead of your ",[603,2324,815],{},[650,2326,2328],{"id":2327},"trade-offs-and-when-not-to","Trade-Offs and When Not To",[590,2330,2331,2337,2338,668],{},[593,2332,2333,2334,2336],{},"Every ",[603,2335,985],{}," layer costs something."," Each one wraps the downstream app in a task and pipes the response through a stream. Five layers is five wrappers on every request, including health checks. If a concern applies to a handful of routes, a dependency is both cheaper and better scoped — the comparison is in ",[644,2339,2341],{"href":2340},"\u002Fcore-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fmiddleware-vs-dependencies-when-to-use-which\u002F","middleware vs dependencies",[590,2343,2344,2347,2348,2350,2351,2354,2355,2359],{},[593,2345,2346],{},"Ordering is a hidden coupling."," ",[603,2349,605],{}," calls scattered across an application factory, a plugin, and a conditional ",[603,2352,2353],{},"if settings.debug"," block produce an order nobody can read off the page. Register the whole stack in one function, in one file, with a comment stating the intended nesting — the discipline the ",[644,2356,2358],{"href":2357},"\u002Fcore-architecture-routing-patterns\u002Fapplication-factory-patterns\u002Ffastapi-app-factory-pattern-for-testing-and-deployment\u002F","application factory pattern"," exists to enforce.",[590,2361,2362,2347,2365,2367],{},[593,2363,2364],{},"A safety-net middleware can hide errors.",[603,2366,1839],{}," above is useful for a demonstration and dangerous in production if it swallows exceptions without re-raising or reporting them. If you add one, log the exception with its traceback and let your error tracker see it before returning the fallback response.",[590,2369,2370,2373,2374,668],{},[593,2371,2372],{},"Middleware ordering does not extend into mounted sub-applications."," A mount is a separate ASGI app with its own stack. Outer middleware wraps it, but middleware registered on the sub-app runs inside, and the sub-app's own exception handlers apply to its routes — a wrinkle worth remembering when using the mounting approach in ",[644,2375,2377],{"href":2376},"\u002Fcore-architecture-routing-patterns\u002Fmodular-router-organization\u002F","modular router organization",[650,2379,2381],{"id":2380},"faq","FAQ",[590,2383,2384,2387,2388,2390],{},[593,2385,2386],{},"Which middleware runs first in FastAPI?","\nThe one added last. ",[603,2389,605],{}," pushes onto the front of the stack, so the final registration becomes the outermost layer and its inbound code runs first. On the way out the order reverses, so it also runs last.",[590,2392,2393,2396,2397,2399],{},[593,2394,2395],{},"Do FastAPI exception handlers run inside or outside middleware?","\nInside. Starlette's ",[603,2398,819],{}," sits between your middleware stack and the router, so an exception raised in an endpoint or dependency is converted to a response there and travels outward through your middleware as an ordinary response.",[590,2401,2402,2405,2406,2408],{},[593,2403,2404],{},"Why does raising HTTPException in middleware return a 500 instead of my status code?","\nBecause the handler that translates ",[603,2407,624],{}," into a response lives inside the middleware stack. An exception raised in middleware is already outside it, so nothing converts it, and it propagates to the server error handler as an unhandled exception.",[590,2410,2411,2414,2415,2417],{},[593,2412,2413],{},"Why did my logging middleware not log a request?","\nBecause a middleware outside it returned a response without calling ",[603,2416,635],{},", or raised. Everything inner is skipped entirely in both cases, which is why a short-circuiting middleware belongs innermost unless you intend to bypass the rest.",[590,2419,2420,2423,2424,2426,2427,2429],{},[593,2421,2422],{},"Where should CORSMiddleware go relative to my own middleware?","\nAdd it last so it becomes the outermost layer. Its headers are then attached to responses produced by anything inside it, including error responses, so a ",[603,2425,815],{}," from an inner middleware still reaches the browser as a ",[603,2428,815],{}," rather than a CORS failure.",[590,2431,2432,2435,2436,2438],{},[593,2433,2434],{},"How do I return a proper status code from middleware?","\nReturn a ",[603,2437,1966],{}," with the status you want instead of raising. Middleware is outside the exception-to-response translation layer, so constructing the response yourself is the only reliable way to control what the client receives.",[650,2440,2442],{"id":2441},"related-reading","Related Reading",[597,2444,2445,2453,2461,2468,2478],{},[600,2446,2447,2347,2450,668],{},[593,2448,2449],{},"Up to the section:",[644,2451,2452],{"href":646},"Middleware Implementation",[600,2454,2455,2347,2458,668],{},[593,2456,2457],{},"Why CORS goes last:",[644,2459,2460],{"href":2163},"CORS Middleware Configuration",[600,2462,2463,2347,2466,668],{},[593,2464,2465],{},"Whether it should be middleware at all:",[644,2467,521],{"href":2340},[600,2469,2470,2347,2473,1910,2475,668],{},[593,2471,2472],{},"What runs inside the exception layer:",[644,2474,485],{"href":1892},[644,2476,2477],{"href":1887},"Customising Validation Error Responses",[600,2479,2480,2347,2483,668],{},[593,2481,2482],{},"Registering the stack in one place:",[644,2484,401],{"href":2357},[2486,2487,2488],"style",{},"html pre.shiki code .sYEJz, html code.shiki .sYEJz{--shiki-default:#032563}html pre.shiki code .sTJeM, html code.shiki .sTJeM{--shiki-default:#A0111F}html pre.shiki code .sigWx, html code.shiki .sigWx{--shiki-default:#0E1116}html pre.shiki code .sacAq, html code.shiki .sacAq{--shiki-default:#023B95}html pre.shiki code .sV4o_, html code.shiki .sV4o_{--shiki-default:#702C00}html pre.shiki code .s3dhs, html code.shiki .s3dhs{--shiki-default:#622CBC}html pre.shiki code .sFeEa, html code.shiki .sFeEa{--shiki-default:#66707B}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);}",{"title":830,"searchDepth":874,"depth":874,"links":2490},[2491,2492,2493,2499,2500,2501,2502,2503],{"id":652,"depth":874,"text":653},{"id":797,"depth":874,"text":798},{"id":848,"depth":874,"text":849,"children":2494},[2495,2496,2497,2498],{"id":1814,"depth":891,"text":1815},{"id":1856,"depth":891,"text":1857},{"id":1897,"depth":891,"text":1898},{"id":1922,"depth":891,"text":1923},{"id":2077,"depth":874,"text":2078},{"id":2175,"depth":874,"text":2176},{"id":2327,"depth":874,"text":2328},{"id":2380,"depth":874,"text":2381},{"id":2441,"depth":874,"text":2442},"2026-07-20","The last middleware added is the outermost. See where FastAPI exception handlers sit in the stack, and why an exception in one middleware bypasses another.","md",[2508,2510,2512,2514,2516],{"q":2386,"a":2509},"The one added last. add_middleware pushes onto the front of the stack, so the final registration becomes the outermost layer and its inbound code runs first. On the way out the order reverses, so it also runs last.",{"q":2395,"a":2511},"Inside. Starlette's ExceptionMiddleware sits between your middleware stack and the router, so an exception raised in an endpoint or dependency is converted to a response there and travels outward through your middleware as an ordinary response.",{"q":2404,"a":2513},"Because the handler that translates HTTPException into a response lives inside the middleware stack. An exception raised in middleware is already outside it, so nothing converts it, and it propagates to the server error handler as an unhandled exception.",{"q":2413,"a":2515},"Because a middleware outside it returned a response without calling call_next, or raised. Everything inner is skipped entirely in both cases, which is why a short-circuiting middleware belongs innermost unless you intend to bypass the rest.",{"q":2422,"a":2517},"Add it last so it becomes the outermost layer. Its headers are then attached to responses produced by anything inside it, including error responses, so a 500 from an inner middleware still reaches the browser as a 500 rather than a CORS failure.",null,{"slug":2520,"breadcrumb":2521},"middleware-execution-order",[2522,2524,2527,2528],{"label":2523,"path":1880},"Home",{"label":2525,"path":2526},"Core Architecture & Routing Patterns","\u002Fcore-architecture-routing-patterns\u002F",{"label":2452,"path":646},{"label":2529,"path":2530},"Middleware Execution Order","\u002Fcore-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fmiddleware-execution-order\u002F",{"title":515,"description":2505},"article","5tueLBdx2vfidOrI8kikzQG5nVAZHDCO1x2nS03rpgs",[2518,2518],1784588202739]