FastAPI Best Practices
Reference guide for writing FastAPI code that follows the official documentation
(https://fastapi.tiangolo.com/). 33 rules across 11 categories, prioritized by impact —
correctness first, structure/validation next, performance and polish last. Each rule
pairs an incorrect example with the official correct pattern and links the relevant
docs page.
Security/authentication and testing are covered by two companion skills —
fastapi-security and fastapi-testing — since they tend to live in their own
files (auth modules, test suites) rather than alongside routing/model code.
When to Apply
Reference these guidelines when:
- Writing new FastAPI endpoints, routers, parameters, or Pydantic models
- Designing dependency injection or error handling
- Adding streaming, background tasks, settings/config, or middleware
- Reviewing or refactoring a FastAPI backend
Rule Categories by Priority
| Priority |
Category |
Impact |
Prefix |
| 1 |
Request/Response Models |
CRITICAL |
model- |
| 2 |
Request Parameters & Validation |
HIGH |
params- |
| 3 |
App Structure & Routing |
HIGH |
structure- |
| 4 |
Dependency Injection |
HIGH |
di- |
| 5 |
Error Handling |
HIGH |
error- |
| 6 |
Async & Concurrency |
MEDIUM |
async- |
| 7 |
Responses & Streaming |
MEDIUM |
response- |
| 8 |
Background Tasks & Config |
MEDIUM |
background-, config- |
| 9 |
Lifespan & Resources |
MEDIUM |
lifespan- |
| 10 |
Middleware & Cross-cutting |
MEDIUM |
mw- |
| 11 |
OpenAPI Documentation |
LOW |
docs- |
Quick Reference
1. Request/Response Models (CRITICAL)
model-response-model — declare a response model on every endpoint
model-separate-input-output — separate input vs output models so secrets never leak
model-return-type-annotation — prefer the return-type annotation; use response_model= only when types differ
model-pydantic-validation — validate with Pydantic Field/Literal, not manual if checks
model-exclude-unset — response_model_exclude_unset for partial/sparse data
2. Request Parameters & Validation (HIGH)
params-query-validation — validate query params with Annotated[..., Query()]
params-path-validation — constrain path params with Annotated[..., Path()] (ge/le)
params-query-param-models — group filter/sort/search params in a Pydantic query model
params-pagination — paginate lists with bounded limit/offset; empty list, not 404
3. App Structure & Routing (HIGH)
structure-apirouter-prefix-tags — give each APIRouter a prefix and tags
structure-bigger-app-layout — split into routers/, models/, dependencies.py
structure-import-submodule — import the submodule, not the router variable
structure-status-code-decorator — set the success status_code in the decorator (201 for creation)
4. Dependency Injection (HIGH)
di-use-depends — share logic via Depends(), not manual instantiation
di-annotated — use Annotated[T, Depends(...)] over the legacy default-value form
di-reusable-type-alias — hoist repeated dependencies into a type alias
di-yield-cleanup — use yield dependencies for setup/teardown (DB sessions, files)
5. Error Handling (HIGH)
error-raise-httpexception — raise HTTPException, never return an error
error-specific-status-codes — use specific codes (404/400/409), not a blanket 500
error-reraise-httpexception — re-raise HTTPException in broad except blocks
error-custom-handler — centralize cross-cutting errors in @app.exception_handler
6. Async & Concurrency (MEDIUM)
async-def-vs-def — choose async def vs def by the library you call
async-no-blocking-in-async — never call blocking code inside async def
async-await-all-io — await every async call
7. Responses & Streaming (MEDIUM)
response-streaming — stream long/LLM responses with StreamingResponse, not buffering
response-additional-responses — document non-200 responses in OpenAPI
8. Background Tasks & Config (MEDIUM)
background-tasks — use BackgroundTasks for post-response work
config-pydantic-settings — read config from env with pydantic-settings + @lru_cache
9. Lifespan & Resources (MEDIUM)
lifespan-context-manager — use the lifespan context manager, not deprecated @app.on_event
lifespan-load-once — load expensive resources (models, indexes) once at startup
10. Middleware & Cross-cutting (MEDIUM)
mw-cors — configure CORSMiddleware with an explicit origin allowlist
mw-custom-http — add timing/request-id/logging via @app.middleware("http")
11. OpenAPI Documentation (LOW)
docs-openapi-metadata — add summary, description, tags, response_description
How to Use
Read the individual rule file for the detailed explanation and before/after example:
rules/model-separate-input-output.md
rules/params-query-param-models.md
rules/di-yield-cleanup.md
rules/error-custom-handler.md
Each rule file contains:
- A short explanation of why it matters, tied to the official docs
- An Incorrect example (the antipattern)
- A Correct example (the official pattern)
- A link to the relevant page on https://fastapi.tiangolo.com/
All examples follow the official FastAPI documentation and avoid deprecated APIs.
1---2name: fastapi-best-practices3description: Use when writing, reviewing, or refactoring FastAPI code — endpoints, APIRouter, query/path parameters, dependencies (Depends), Pydantic request/response models, error handling, async def vs def, lifespan events, streaming, background tasks, settings, middleware, or CORS. Triggers on FastAPI backend work, route design, or API code review. For auth/security see fastapi-security; for tests see fastapi-testing.4license: MIT5---67# FastAPI Best Practices89Reference guide for writing FastAPI code that follows the official documentation10(https://fastapi.tiangolo.com/). 33 rules across 11 categories, prioritized by impact —11correctness first, structure/validation next, performance and polish last. Each rule12pairs an incorrect example with the official correct pattern and links the relevant13docs page.1415Security/authentication and testing are covered by two companion skills —16`fastapi-security` and `fastapi-testing` — since they tend to live in their own17files (auth modules, test suites) rather than alongside routing/model code.1819## When to Apply2021Reference these guidelines when:22- Writing new FastAPI endpoints, routers, parameters, or Pydantic models23- Designing dependency injection or error handling24- Adding streaming, background tasks, settings/config, or middleware25- Reviewing or refactoring a FastAPI backend2627## Rule Categories by Priority2829| Priority | Category | Impact | Prefix |30|----------|----------|--------|--------|31| 1 | Request/Response Models | CRITICAL | `model-` |32| 2 | Request Parameters & Validation | HIGH | `params-` |33| 3 | App Structure & Routing | HIGH | `structure-` |34| 4 | Dependency Injection | HIGH | `di-` |35| 5 | Error Handling | HIGH | `error-` |36| 6 | Async & Concurrency | MEDIUM | `async-` |37| 7 | Responses & Streaming | MEDIUM | `response-` |38| 8 | Background Tasks & Config | MEDIUM | `background-`, `config-` |39| 9 | Lifespan & Resources | MEDIUM | `lifespan-` |40| 10 | Middleware & Cross-cutting | MEDIUM | `mw-` |41| 11 | OpenAPI Documentation | LOW | `docs-` |4243## Quick Reference4445### 1. Request/Response Models (CRITICAL)4647- `model-response-model` — declare a response model on every endpoint48- `model-separate-input-output` — separate input vs output models so secrets never leak49- `model-return-type-annotation` — prefer the return-type annotation; use `response_model=` only when types differ50- `model-pydantic-validation` — validate with Pydantic `Field`/`Literal`, not manual `if` checks51- `model-exclude-unset` — `response_model_exclude_unset` for partial/sparse data5253### 2. Request Parameters & Validation (HIGH)5455- `params-query-validation` — validate query params with `Annotated[..., Query()]`56- `params-path-validation` — constrain path params with `Annotated[..., Path()]` (`ge`/`le`)57- `params-query-param-models` — group filter/sort/search params in a Pydantic query model58- `params-pagination` — paginate lists with bounded `limit`/`offset`; empty list, not 4045960### 3. App Structure & Routing (HIGH)6162- `structure-apirouter-prefix-tags` — give each `APIRouter` a `prefix` and `tags`63- `structure-bigger-app-layout` — split into `routers/`, `models/`, `dependencies.py`64- `structure-import-submodule` — import the submodule, not the `router` variable65- `structure-status-code-decorator` — set the success `status_code` in the decorator (201 for creation)6667### 4. Dependency Injection (HIGH)6869- `di-use-depends` — share logic via `Depends()`, not manual instantiation70- `di-annotated` — use `Annotated[T, Depends(...)]` over the legacy default-value form71- `di-reusable-type-alias` — hoist repeated dependencies into a type alias72- `di-yield-cleanup` — use `yield` dependencies for setup/teardown (DB sessions, files)7374### 5. Error Handling (HIGH)7576- `error-raise-httpexception` — `raise HTTPException`, never `return` an error77- `error-specific-status-codes` — use specific codes (404/400/409), not a blanket 50078- `error-reraise-httpexception` — re-raise `HTTPException` in broad `except` blocks79- `error-custom-handler` — centralize cross-cutting errors in `@app.exception_handler`8081### 6. Async & Concurrency (MEDIUM)8283- `async-def-vs-def` — choose `async def` vs `def` by the library you call84- `async-no-blocking-in-async` — never call blocking code inside `async def`85- `async-await-all-io` — `await` every async call8687### 7. Responses & Streaming (MEDIUM)8889- `response-streaming` — stream long/LLM responses with `StreamingResponse`, not buffering90- `response-additional-responses` — document non-200 responses in OpenAPI9192### 8. Background Tasks & Config (MEDIUM)9394- `background-tasks` — use `BackgroundTasks` for post-response work95- `config-pydantic-settings` — read config from env with `pydantic-settings` + `@lru_cache`9697### 9. Lifespan & Resources (MEDIUM)9899- `lifespan-context-manager` — use the `lifespan` context manager, not deprecated `@app.on_event`100- `lifespan-load-once` — load expensive resources (models, indexes) once at startup101102### 10. Middleware & Cross-cutting (MEDIUM)103104- `mw-cors` — configure `CORSMiddleware` with an explicit origin allowlist105- `mw-custom-http` — add timing/request-id/logging via `@app.middleware("http")`106107### 11. OpenAPI Documentation (LOW)108109- `docs-openapi-metadata` — add `summary`, `description`, `tags`, `response_description`110111## How to Use112113Read the individual rule file for the detailed explanation and before/after example:114115```116rules/model-separate-input-output.md117rules/params-query-param-models.md118rules/di-yield-cleanup.md119rules/error-custom-handler.md120```121122Each rule file contains:123- A short explanation of why it matters, tied to the official docs124- An **Incorrect** example (the antipattern)125- A **Correct** example (the official pattern)126- A link to the relevant page on https://fastapi.tiangolo.com/127128All examples follow the official FastAPI documentation and avoid deprecated APIs.