FastAPI is a modern, high-performance Python web framework for building APIs based on standard Python type hints. It's built on top of Starlette for web handling and Pydantic for data validation, providing automatic API documentation, async support, and excellent developer experience. FastAPI excels at creating production-ready RESTful APIs quickly while maintaining type safety and performance. One key distinction: FastAPI's automatic validation and documentation generation from type hints eliminates boilerplate code that other frameworks require manually, making it both fast to develop with and fast to execute.
19 tables, 155 concepts. Select a concept node to jump to its table row.
Table 1: Core Application Setup
Everything starts with the FastAPI() instance and the path-operation decorators that hang routes off it. These are the pieces you write in your very first file — creating the app, defining an endpoint, choosing sync or async, and wiring up startup logic with a lifespan handler. The automatic /docs and Pydantic integration listed here are also why FastAPI feels productive from line one.
| Concept | Example | Description |
|---|---|---|
app = FastAPI()app = FastAPI(title="My API", version="1.0.0") | • Creates the main application instance • accepts optional metadata like title, description, version for OpenAPI docs. | |
| • Defines an HTTP endpoint with method and path • decorator tells FastAPI to handle requests to this route. | |
def read_root(): return {"msg": "Hello"} | • Function executed when endpoint is hit • return value is automatically serialized to JSON. | |
fastapi dev main.pyfastapi run main.py | • Recommended way to run FastAPI (included in fastapi[standard])• dev for development (auto-reload, localhost only); run for production (no reload, 0.0.0.0). | |
uvicorn main:app --reload | • Starts the ASGI server directly; preferred for containerized or programmatic use • fastapi dev/fastapi run are now the recommended alternatives. | |
async def read_items(): return await db.fetch() | • Uses async def for non-blocking I/O operations• better performance for I/O-bound tasks. | |
from contextlib import asynccontextmanagerasync def lifespan(app): # startup yield # shutdownapp = FastAPI(lifespan=lifespan) | • Manages startup and shutdown logic • replaces deprecated on_event decorators with single context manager. | |
/docs for Swagger UI/redoc for ReDoc | • FastAPI auto-generates interactive API docs at these endpoints based on your code • requires zero configuration. | |
class Item(BaseModel): name: str price: float | • Pydantic models provide automatic validation and serialization • FastAPI uses them for request/response schemas. |
Table 2: HTTP Methods and Route Definitions
Each HTTP method maps to a decorator and a conventional meaning — GET reads, POST creates, PUT replaces, PATCH partially updates, DELETE removes. Following these conventions keeps your API predictable and lets caches, browsers, and other tools behave correctly. The lesser-used HEAD and OPTIONS round out the set for existence checks and CORS handling.
| Method | Example | Description |
|---|---|---|
def read_item(id: int): | • Retrieves data • no request body, typically uses path or query parameters. | |
def create_item(item: Item): | • Creates new resource • accepts request body defined by Pydantic model. | |
def update_item(id: int, item: Item): | • Replaces entire resource • expects complete object in request body. | |
def partial_update(id: int, item: PartialItem): | • Updates partial resource • only specified fields are modified. | |
def delete_item(id: int): | • Removes resource • typically returns status confirmation. | |
def items_head(): | • Returns headers only, no body • useful for checking resource existence or metadata. | |
def items_options(): | • Returns allowed HTTP methods for endpoint • often handled automatically for CORS. |
Table 3: Request Parameters
Data reaches your endpoint from several places — the URL path, the query string, the JSON body, headers, cookies, form fields, and uploaded files — and FastAPI pulls each one in just by declaring it as a typed function argument. The standout feature is that the type annotation alone drives parsing and validation, so a path parameter declared as int is automatically rejected if someone passes text. Knowing which source maps to which declaration is the core skill this table builds.
| Type | Example | Description |
|---|---|---|
from typing import Annotatedq: Annotated[str | None, Query(max_length=50)] = None | • Recommended modern syntax (since FastAPI 0.95.0) for declaring parameters with validation • keeps type annotation and metadata together. | |
def read(item_id: int): | • Extracts value from URL path • type validated automatically based on annotation. | |
def read_items(skip: int = 0, limit: int = 10): | • Reads from URL query string ( ?skip=0&limit=10)• optional with defaults. | |
def create(item: Item): | • Accepts JSON payload • Pydantic model validates structure and types automatically. | |
def create(item: Item, user: User): | • Accepts multiple JSON objects in request body • FastAPI expects nested structure. | |
from fastapi import Headerdef read(user_agent: str = Header()): | • Reads HTTP headers • automatically converts hyphen-case to snake_case. | |
from fastapi import Cookiedef read(session_id: str = Cookie()): | • Extracts cookie values • type-validated like other parameters. | |
from fastapi import Formdef login(username: str = Form()): | • Accepts application/x-www-form-urlencoded• requires python-multipart installed. | |
from fastapi import UploadFiledef upload(file: UploadFile): | • Handles file uploads • UploadFile provides async methods and metadata like filename. | |
def upload(files: list[UploadFile]): | • Accepts multiple files in single request • validated as list. |
Table 4: Parameter Validation
Beyond checking types, you often need to enforce real constraints — a minimum length, a numeric range, a regex pattern, a required-versus-optional rule. The modern Annotated syntax shown in the first rows is the recommended way to attach these rules while keeping the type and its metadata in one place. Push validation this far up the stack and your endpoint body can trust that the data is already clean.
| Technique | Example | Description |
|---|---|---|
from typing import Annotatedq: Annotated[str | None, Query(min_length=3)] = None | • Recommended way to use Query, Path, Header, Cookie • type annotation and validation rules stay together. | |
from fastapi import Queryq: str = Query(min_length=3) | Applies constraints like min/max length, regex patterns to query parameters. | |
from fastapi import Pathid: int = Path(ge=1, le=1000) | Validates path parameters with numeric constraints like ge (greater/equal), le (less/equal). | |
q: Annotated[str, Query(min_length=1)] | • Modern approach: no default = required when using Annotated • older approach: q: str = Query(...) with ellipsis. | |
q: str | None = None | • Python 3.10+ union syntax for optional parameters • older versions use Optional[str]. | |
tags: list[str] = Query([]) | • Accepts multiple values for same parameter ( ?tags=a&tags=b)• validated as list. | |
item_query: str = Query(alias="item-query") | Maps hyphenated URL param to snake_case Python variable. | |
old_param: str = Query(deprecated=True) | • Marks parameter as deprecated in OpenAPI docs • still functional but discouraged. | |
code: str = Query(pattern="^[A-Z]{3}$") | Validates string parameter against regex pattern. | |
price: float = Query(gt=0, le=9999.99) | Applies gt (greater than), lt (less than), ge, le constraints to numbers. |
Table 5: Pydantic Models and Validation
Pydantic models are the heart of how FastAPI validates and serializes structured data — a BaseModel subclass defines the shape of a request or response, and FastAPI does the rest. This table covers the tools for going beyond simple fields: per-field constraints, custom field and model validators for logic that types can't express, computed fields, and model inheritance for reuse. Note this is Pydantic v2, so model_config and field_validator replace the older patterns you may have seen.
| Concept | Example | Description |
|---|---|---|
class Item(BaseModel): name: str price: float | • Base class for all Pydantic models • provides automatic validation and serialization. | |
from pydantic import Fieldprice: float = Field(gt=0) | • Adds validation rules directly to model fields • similar to Query/Path but for models. | |
name: str = Field(description="Item name") | • Adds documentation to field • appears in OpenAPI schema. | |
price: float = Field(examples=[9.99, 19.99]) | Provides example values for API documentation and testing. | |
def check_email(cls, v): | • Custom validation logic for specific field • runs after type validation. | |
def check_passwords(self): | • Validates entire model after individual fields • useful for cross-field validation. | |
from pydantic import ConfigDictmodel_config = ConfigDict(str_strip_whitespace=True) | • Configures model behavior using Pydantic v2's ConfigDict• replaces the old inner class Config. | |
description: str | None = None | • Field can be omitted or null • uses Python union type or Optional. | |
def total(self) -> float: | • Adds calculated field to model • included in serialization but not validation. | |
class ExtendedItem(Item): category: str | • Inherits fields from base model • enables model reuse and extension. |
Table 6: Response Configuration
Controlling what goes back to the client is just as important as parsing what comes in. The response_model and return-type annotation filter and validate your output — handy for stripping a password field before it ever leaves the server — while the various response classes let you return HTML, files, redirects, or streamed data instead of plain JSON. Status codes, custom headers, and cookies round out the toolkit for shaping a precise response.
| Technique | Example | Description |
|---|---|---|
| • Specifies return type for validation and docs • filters response to match model. | |
async def create_item(item: Item) -> Item: | • Modern preferred approach for response type declaration • FastAPI uses the return annotation for validation, filtering, and docs. | |
response_model=User, response_model_exclude={"password"} | • Removes specified fields from response • useful for hiding sensitive data. | |
| • Sets HTTP status code for response • default is 200 for GET, 201 for POST recommended. | |
from fastapi.responses import JSONResponsereturn JSONResponse(content=data) | Explicit JSON response with custom headers or status code. | |
from fastapi.responses import HTMLResponsereturn HTMLResponse(content=html) | • Returns HTML content • sets Content-Type to text/html. | |
from fastapi.responses import RedirectResponsereturn RedirectResponse(url="/new") | • HTTP redirect to different URL • status_code defaults to 307. | |
from fastapi.responses import FileResponsereturn FileResponse(path="file.pdf") | • Serves static files • handles content-type and download headers automatically. | |
from fastapi.responses import StreamingResponsereturn StreamingResponse(generator) | • Streams large data in chunks • memory-efficient for big responses. | |
from fastapi.responses import ORJSONResponsereturn ORJSONResponse(content=data) | • Uses faster orjson library for JSON serialization • 2-3x performance improvement. | |
from fastapi import Responseresponse.headers["X-Custom"] = "value" | Sets custom HTTP headers in response. | |
response.set_cookie(key="session", value="abc") | • Adds cookie to response • supports httponly, secure, samesite options. |
Table 7: Dependency Injection
Dependency injection is one of FastAPI's most loved features — Depends() lets you factor out reusable logic like database sessions, authentication, and pagination, then declare it as a parameter wherever you need it. Dependencies can nest, run as yield generators that clean up after the response, apply at the route, router, or app level, and be swapped out entirely for testing. Lean on this pattern and your endpoints stay thin while shared concerns live in one place.
| Pattern | Example | Description |
|---|---|---|
from typing import Annotateddef route(db: Annotated[DB, Depends(get_db)]): | • Function providing reusable logic • recommended Annotated syntax (since 0.95.0) keeps type and dependency together. | |
DBDep = Annotated[Session, Depends(get_db)]def endpoint(db: DBDep): | • Defines reusable type alias for common dependency • reduces repetition across many routes. | |
async def get_session(): async with Session() as s: yield s | • Async generator dependency • cleanup happens after response using yield. | |
class Pagination: def __init__(self, skip=0): | • Class instances as dependencies • __init__ parameters become sub-dependencies. | |
def get_user(token = Depends(get_token)): | • Dependency can depend on other dependencies • creates dependency chain. | |
app = FastAPI(dependencies=[Depends(verify)]) | • Applied to all routes in application • useful for auth, logging. | |
router = APIRouter(dependencies=[Depends(check)]) | • Applied to all routes in router • scoped to specific module. | |
def get_db(): db = Database() yield db db.close() | • Provides resource cleanup after request • code after yield runs as teardown. | |
app.dependency_overrides[get_db] = mock_db | • Replaces dependency for testing • allows mocking without changing code. | |
| • Dependency executed but result not used in function • for side effects only. |
Table 8: Error Handling
When something goes wrong, you want a clean, consistent error response rather than a raw stack trace. Raising HTTPException is the everyday move for returning a 404 or 403, while custom exceptions paired with exception handlers let you centralize how whole categories of errors turn into responses. You can even override FastAPI's default validation-error format when you need errors shaped a particular way.
| Approach | Example | Description |
|---|---|---|
from fastapi import HTTPExceptionraise HTTPException(status_code=404, detail="Not found") | • Built-in exception for HTTP errors • automatically formatted as JSON response. | |
class CustomError(Exception): pass | • Define custom exception class • can add custom handler. | |
def handler(request, exc): | • Catches specific exception type globally • returns custom response. | |
from fastapi.exceptions import RequestValidationError | Customizes Pydantic validation error response format. | |
raise HTTPException(status_code=403, detail="Forbidden") | • Sets HTTP status code directly in exception • common codes: 400, 401, 403, 404, 500. | |
raise HTTPException(status_code=401, headers={"WWW-Authenticate": "Bearer"}) | • Adds custom headers to error response • useful for auth challenges. | |
from starlette.exceptions import HTTPException as StarletteHTTPException | Replaces FastAPI's default error formatting. |
Table 9: Authentication and Security
FastAPI ships with security utilities that plug straight into its dependency system and its OpenAPI docs. The rows walk through the common building blocks — OAuth2 password flows, API keys in headers or cookies, JWT creation and verification, password hashing with bcrypt — and how they combine into a get_current_user dependency you reuse across protected routes. Security scopes at the end add fine-grained, permission-based access on top.
| Mechanism | Example | Description |
|---|---|---|
from fastapi.security import OAuth2PasswordBeareroauth2 = OAuth2PasswordBearer(tokenUrl="token") | • Standard OAuth2 flow for password-based authentication • extracts token from Authorization header. | |
from fastapi.security import OAuth2PasswordRequestFormdef login(form: OAuth2PasswordRequestForm = Depends()): | • Form data model for username/password login • includes username, password, optional scope. | |
from fastapi.security import APIKeyHeaderapi_key = APIKeyHeader(name="X-API-Key") | • Extracts API key from custom header • name specifies header name. | |
from fastapi.security import APIKeyQueryapi_key = APIKeyQuery(name="api_key") | • Reads API key from query parameter • less secure than header. | |
from fastapi.security import APIKeyCookieapi_key = APIKeyCookie(name="session") | Extracts API key from cookie. | |
from jose import jwttoken = jwt.encode(data, SECRET, algorithm="HS256") | • Encodes JWT token with payload and secret • requires python-jose library. | |
from jose import JWTError, jwtpayload = jwt.decode(token, SECRET, algorithms=["HS256"]) | • Decodes and verifies JWT • raises JWTError if invalid. | |
from passlib.context import CryptContextpwd_context = CryptContext(schemes=["bcrypt"]) | • Hashes passwords with bcrypt • never store plain passwords. | |
async def get_current_user(token = Depends(oauth2)): # decode token, fetch user | • Dependency that extracts and validates user from token • reusable across routes. | |
| • Permission-based access control • validates OAuth2 scopes. |
Table 10: Middleware
Middleware wraps every request and response, making it the right place for cross-cutting concerns that shouldn't live in individual endpoints. CORS is the one nearly everyone needs first, while the others handle compression, HTTPS enforcement, trusted-host checks, and sessions — plus the option to write your own function- or class-based middleware for custom logic like timing or request logging.
| Type | Example | Description |
|---|---|---|
from fastapi.middleware.cors import CORSMiddlewareapp.add_middleware(CORSMiddleware, allow_origins=["*"]) | • Enables Cross-Origin Resource Sharing • configures which domains can access API. | |
async def add_header(request, call_next): | • Function-based middleware • runs before and after every request. | |
class TimingMiddleware(BaseHTTPMiddleware): async def dispatch(self, request, call_next): | • Class-based middleware • more complex logic, state management. | |
from starlette.middleware.trustedhost import TrustedHostMiddlewareapp.add_middleware(TrustedHostMiddleware, allowed_hosts=["*.example.com"]) | • Validates Host header • prevents HTTP Host header attacks. | |
from starlette.middleware.httpsredirect import HTTPSRedirectMiddlewareapp.add_middleware(HTTPSRedirectMiddleware) | • Redirects HTTP to HTTPS • enforces secure connections. | |
from fastapi.middleware.gzip import GZipMiddlewareapp.add_middleware(GZipMiddleware, minimum_size=1000) | • Compresses responses over specified size • reduces bandwidth. | |
from starlette.middleware.sessions import SessionMiddlewareapp.add_middleware(SessionMiddleware, secret_key="key") | Adds session support via signed cookies. |
Table 11: Background Tasks
Some work — sending a confirmation email, writing a log — shouldn't make the user wait for the response. FastAPI's built-in BackgroundTasks handles the lightweight cases right inside the same process, no extra infrastructure required. For heavier or long-running jobs that need their own workers, the table points you to Celery and ARQ as the next step up.
| Approach | Example | Description |
|---|---|---|
from fastapi import BackgroundTasksdef task(email: str): send_email(email) | • Simple background function executed after response • no external queue needed. | |
def endpoint(bg: BackgroundTasks): bg.add_task(send_email, to="user@example.com") | • Schedules function to run after response sent • tasks run in same process. | |
bg.add_task(task1)bg.add_task(task2) | Multiple tasks run sequentially in order added. | |
def task(db = Depends(get_db)): | • Background task can use dependency injection • dependencies resolved when task runs. | |
from celery import Celerycelery = Celery("tasks", broker="redis://localhost")def heavy_task(): | • Distributed task queue for heavy/long-running jobs • runs in separate workers outside request cycle. | |
from arq import create_poolredis = await create_pool() | • Redis-based async task queue • alternative to Celery for async-first applications. |
Table 12: Database Integration
Most FastAPI apps talk to a database through SQLAlchemy, and the choice between async and sync sessions sets the tone for everything else. These rows cover defining ORM models, running queries the SQLAlchemy 2.0 way, injecting a session as a dependency so it closes cleanly after each request, and managing schema changes with Alembic. Connection pooling at the end is the detail that keeps a busy API from exhausting the database.
| Technique | Example | Description |
|---|---|---|
from sqlalchemy.ext.asyncio import AsyncSessionasync def get_db(): yield session | • Async database session for non-blocking DB operations • requires async driver. | |
from sqlalchemy.orm import Sessiondef get_db(): yield db | • Synchronous session • simpler but blocks during queries. | |
from sqlalchemy import Column, Integer, Stringclass User(Base): id = Column(Integer, primary_key=True) | • ORM model defining table structure • SQLAlchemy maps to database tables. | |
result = await session.execute(select(User))users = result.scalars().all() | • Executes async query • uses SQLAlchemy 2.0 style. | |
def endpoint(db: Session = Depends(get_db)): | • Injects database session into route • session closed automatically after request. | |
alembic init alembicalembic revision --autogeneratealembic upgrade head | • Database schema migrations • tracks and applies schema changes over time. | |
engine = create_async_engine(url, pool_size=20, max_overflow=10) | • Reuses database connections • critical for production performance. |
Table 13: WebSockets and Real-Time Communication
When you need a live, two-way channel — chat, notifications, a streaming dashboard — a plain request/response cycle won't do. FastAPI's WebSocket support gives you the full lifecycle: accept the connection, receive and send text or JSON in a loop, and handle the disconnect cleanly. The final SSE row is the simpler choice when updates only need to flow one way, from server to client.
| Feature | Example | Description |
|---|---|---|
from fastapi import WebSocketasync def websocket_endpoint(websocket: WebSocket): | • Defines WebSocket route • full-duplex communication channel. | |
await websocket.accept() | • Establishes WebSocket connection • must be called before send/receive. | |
data = await websocket.receive_text()data = await websocket.receive_json() | • Reads message from client • supports text, JSON, or bytes. | |
await websocket.send_text("Hello")await websocket.send_json({"msg": "Hi"}) | Sends message to client. | |
try: while True: await websocket.receive_text()except WebSocketDisconnect: | • Handles client disconnect • exception raised when connection closes. | |
from fastapi.responses import StreamingResponsedef event_stream(): yield f"data: {msg}\n\n" | • One-way streaming from server to client • simpler than WebSockets for uni-directional updates. |
Table 14: Testing
FastAPI makes testing genuinely pleasant because TestClient runs your app in-process — no server to spin up. These rows show the standard pytest workflow, the context-manager form that triggers lifespan events, the httpx-based async client for testing async code properly, and the dependency-override trick that swaps a real database for a mock without touching application code.
| Tool | Example | Description |
|---|---|---|
from fastapi.testclient import TestClientclient = TestClient(app)response = client.get("/") | • Synchronous test client • tests FastAPI without running server. | |
def test_read_main(): response = client.get("/") assert response.status_code == 200 | • Standard pytest patterns work with TestClient • uses fixtures for setup. | |
with TestClient(app) as client: response = client.get("/") | • Triggers lifespan events during testing • startup/shutdown run automatically. | |
from httpx import AsyncClientasync with AsyncClient(app=app) as client: response = await client.get("/") | • Async testing with httpx • required for testing async dependencies properly. | |
app.dependency_overrides[get_db] = lambda: mock_db | • Replaces real dependency with mock for testing • no code changes needed. | |
files = {"file": ("test.txt", b"content", "text/plain")}client.post("/upload", files=files) | • Simulates file upload in tests • tuple format: (filename, content, content-type). | |
def client(): return TestClient(app) | • Reusable test setup • pytest fixture creates client for all tests. |
Table 15: Advanced Routing
As an app grows past a single file, APIRouter is how you split routes into focused modules with shared prefixes, tags, and dependencies, then mount them onto the main app. Sub-application mounting and static file serving extend the same idea, and the route-priority row captures a subtle gotcha — define /users/me before /users/{id}, or the parameterized route swallows it.
| Concept | Example | Description |
|---|---|---|
from fastapi import APIRouterrouter = APIRouter(prefix="/items", tags=["items"]) | • Modular routing • groups related endpoints with shared prefix and tags. | |
app.include_router(router) | • Mounts router to main app • enables splitting large apps into modules. | |
app.mount("/v1", app_v1) | • Mounts independent FastAPI app at path • useful for API versioning. | |
from fastapi.staticfiles import StaticFilesapp.mount("/static", StaticFiles(directory="static")) | Serves static files from directory. | |
router = APIRouter(dependencies=[Depends(verify_token)]) | • Applies dependency to all routes in router • cleaner than per-route. | |
router = APIRouter(tags=["items"]) | • Groups endpoints in OpenAPI docs • creates collapsible sections. | |
Define more specific routes first: | • FastAPI matches first matching route • specific paths before parameterized ones. |
Table 16: OpenAPI Customization
FastAPI generates an OpenAPI schema and interactive docs for free, but real projects want to shape them. These rows let you set titles and descriptions, group endpoints under documented tags, add example responses, deprecate old routes, change or disable the docs URLs, and tweak Swagger UI behavior. Polishing the generated docs is what turns a working API into one other developers actually enjoy consuming.
| Element | Example | Description |
|---|---|---|
app = FastAPI(title="My API", description="Docs", version="1.0") | Sets API title, description, version in OpenAPI schema and docs. | |
tags_metadata = [{"name": "items", "description": "Item operations"}]app = FastAPI(openapi_tags=tags_metadata) | Adds descriptions to tag groups in documentation. | |
def custom_openapi(): # modify schemaapp.openapi = custom_openapi | • Overrides OpenAPI generation • add custom extensions or modify schema. | |
app = FastAPI(docs_url=None, redoc_url=None) | • Hides documentation endpoints • use in production if needed. | |
app = FastAPI(docs_url="/api/docs") | Changes Swagger UI path from default /docs. | |
| • Sets unique operation identifier in OpenAPI • useful for code generation tools. | |
| • Marks endpoint as deprecated in docs • still functional. | |
responses={200: {"description": "OK", "content": {"application/json": {"example": {...}}}}} | Adds example responses to OpenAPI docs. | |
app = FastAPI(separate_input_output_schemas=False) | • FastAPI 0.102.0+ generates separate OpenAPI schemas for input and output with Pydantic v2 • set False to disable. | |
app = FastAPI(swagger_ui_parameters={"defaultModelsExpandDepth": -1}) | Customizes Swagger UI behavior like theme, syntax highlighting, model expansion. |
Table 17: Settings and Configuration
Pydantic Settings brings the same type-safe validation you use for requests to your application config, reading values from environment variables and .env files and failing fast at startup if something's missing. Injecting settings as a dependency keeps them easy to mock in tests, and caching them with lru_cache means the config is parsed once. This is Pydantic Settings v2, so SettingsConfigDict replaces the old inner Config class.
| Technique | Example | Description |
|---|---|---|
from pydantic_settings import BaseSettingsclass Settings(BaseSettings): api_key: str | • Type-safe configuration from environment variables • validates config at startup. | |
from pydantic_settings import SettingsConfigDictmodel_config = SettingsConfigDict(env_file=".env") | • Loads environment variables from .env using Pydantic Settings v2 • replaces old inner class Config: env_file = ".env" pattern. | |
def get_settings(): return Settings()settings: Settings = Depends(get_settings) | • Injects settings into routes • enables easy mocking in tests. | |
def get_settings(): return Settings() | • Loads settings once and caches • improves performance. | |
class Settings(BaseSettings): env: str = "dev" database_url: str | • Uses different config per environment • set via ENV variable. |
Table 18: Performance Optimization
FastAPI is already fast, but these levers squeeze out more under real load. Writing I/O-bound handlers as async unlocks concurrency, Redis caching and HTTP cache headers cut repeated work, connection pooling protects the database, and ORJSON speeds up serialization. The deployment rows — Uvicorn workers, Gunicorn, and the Docker pattern — cover putting all of that into production across multiple CPU cores.
| Strategy | Example | Description |
|---|---|---|
async def fetch_data(): return await http_client.get(url) | • Use async def for I/O-bound operations• enables concurrent request handling. | |
from fastapi_cache import FastAPICachefrom fastapi_cache.backends.redis import RedisBackend | • Caches responses in Redis • reduces database load for repeated queries. | |
| • Sets HTTP caching headers • clients/proxies cache responses. | |
engine = create_engine(url, pool_size=20) | • Reuses database connections • critical for high-throughput APIs. | |
app = FastAPI(default_response_class=ORJSONResponse) | • Uses faster JSON library • 2-3x performance improvement over standard json. | |
uvicorn main:app --workers 4 | • Runs multiple worker processes • utilizes multiple CPU cores. | |
gunicorn main:app --workers 4 --worker-class uvicorn.workers.UvicornWorker | • Process manager with Uvicorn workers • production-grade deployment. | |
FROM python:3.14RUN pip install fastapi[standard]CMD ["fastapi", "run", "main.py", "--port", "80"] | • Official Docker image pattern for FastAPI production deployment • uses fastapi run included in fastapi[standard]. |
Table 19: Advanced Patterns
These are the extras you reach for once the basics are in place and the app is heading toward production. Rate limiting protects you from abuse, API versioning lets the interface evolve without breaking clients, and a GraphQL endpoint or class-based views fit specialized needs. Request-ID tracking and alternative serialization like MessagePack round out a toolkit for APIs that need to scale and be observable.
| Pattern | Example | Description |
|---|---|---|
from strawberry.fastapi import GraphQLRouterapp.include_router(GraphQLRouter(schema), prefix="/graphql") | • Adds GraphQL endpoint using Strawberry • enables flexible query language. | |
from slowapi import Limiterdef endpoint(): | • Throttles requests per IP/user • prevents API abuse. | |
app.mount("/v1", v1_app)app.mount("/v2", v2_app) | • Versions API via URL path • most common versioning strategy. | |
version = request.headers.get("API-Version", "v1") | • Reads version from custom header • cleaner URLs but less discoverable. | |
from fastapi_utils.cbv import cbvclass ItemAPI: | • Groups related endpoints in class • reduces boilerplate for CRUD operations. | |
from msgpack_asgi import MessagePackMiddlewareapp.add_middleware(MessagePackMiddleware) | • Uses binary format for smaller payloads • faster than JSON for large data. | |
async def add_request_id(request, call_next): | • Adds unique ID to each request • enables distributed tracing. |