# Fastapi Patterns

> Production FastAPI patterns with Pydantic v2, async endpoints, OAuth2/JWT, dependency injection, testing, and Kubernetes deployment Use when this capability is needed.

- Skill: `tomevault-io/fastapi-patterns` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add tomevault-io/fastapi-patterns`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tomevault-io/fastapi-patterns/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: tomevault-io (https://skillmd.com/u/tomevault-io)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/tomevault-io/fastapi-patterns

---


# FastAPI Production Patterns

## Description

Modern FastAPI development patterns for 2026, covering the full spectrum from Pydantic v2 migration to production deployment. Validated against 15+ production sources with 0.95 confidence. This skill keeps you on the current side of breaking changes and equips you with battle-tested patterns for async, security, testing, and performance.

## Usage

Install this skill to get production-ready FastAPI patterns including:
- Pydantic v2 migration guide (breaking changes, 4-17x performance gains)
- Async vs sync endpoint decision rules
- OAuth2 + JWT authentication patterns
- Dependency override testing strategies
- Docker + Kubernetes deployment with health probes

When working on FastAPI projects, this skill provides context for:
- Migrating from Pydantic v1 to v2 without breaking existing code
- Setting up proper lifespan events instead of deprecated `@app.on_event()`
- Structuring tests with `TestClient` (sync) and `AsyncClient` (async)
- Configuring Gunicorn + Uvicorn workers for production

## Key Patterns

### Pydantic v2 Migration

```python
# BEFORE (Pydantic v1 — deprecated)
from pydantic import BaseModel, validator

class User(BaseModel):
    name: str

    @validator('name')
    def name_must_not_be_empty(cls, v):
        if not v:
            raise ValueError('name cannot be empty')
        return v

# AFTER (Pydantic v2 — current)
from pydantic import BaseModel, field_validator

class User(BaseModel):
    name: str

    @field_validator('name')
    @classmethod
    def name_must_not_be_empty(cls, v):
        if not v:
            raise ValueError('name cannot be empty')
        return v
```

Method renames (v1 to v2): `parse_obj()` -> `model_validate()`, `dict()` -> `model_dump()`, `json()` -> `model_dump_json()`, `orm_mode = True` -> `model_config = ConfigDict(from_attributes=True)`, `conint(ge=0)` -> `Annotated[int, Field(ge=0)]`

### Lifespan Events (Modern Pattern)

```python
from contextlib import asynccontextmanager
from fastapi import FastAPI

ml_models = {}

@asynccontextmanager
async def lifespan(app: FastAPI):
    # Startup: load resources
    ml_models["answer"] = load_ml_model()
    yield
    # Shutdown: release resources
    ml_models.clear()

app = FastAPI(lifespan=lifespan)
```

Do NOT use deprecated `@app.on_event()`. Use lifespan for: DB connection pools, ML models, resource cleanup.

### Async Decision Rule

```python
# Use async def for: asyncpg, motor, httpx (non-blocking I/O)
@app.get('/users/')
async def get_users():
    users = await async_db.fetch('SELECT * FROM users')
    return users

# Use def for: psycopg2, pymongo, requests (blocking libs)
# FastAPI automatically runs def endpoints in a threadpool
@app.get('/users/sync')
def get_users_sync():
    return sync_db.query('SELECT * FROM users')
```

Rule of thumb: If unsure, use `def`. FastAPI handles the threadpool for you.

### OAuth2 + JWT Authentication

```python
from fastapi import Depends, HTTPException
from fastapi.security import OAuth2PasswordBearer
from jose import jwt

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
SECRET_KEY = "your-secret-key"
ALGORITHM = "HS256"

async def get_current_user(token: str = Depends(oauth2_scheme)):
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username = payload.get("sub")
        if not username:
            raise HTTPException(status_code=401)
        return username
    except:
        raise HTTPException(status_code=401)
```

### CORS (Never Use wildcard in Production)

```python
from fastapi.middleware.cors import CORSMiddleware

app.add_middleware(
    CORSMiddleware,
    allow_origins=["https://example.com"],  # NOT ["*"]
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)
```

### Async Testing with httpx

```python
import pytest
from httpx import ASGITransport, AsyncClient

@pytest.mark.anyio
async def test_async_endpoint():
    async with AsyncClient(
        transport=ASGITransport(app=app),
        base_url="http://test"
    ) as ac:
        response = await ac.get("/")
    assert response.status_code == 200
```

### Dependency Overrides for Mocking

```python
async def override_get_db():
    db = Database("sqlite:///:memory:")
    yield db
    await db.disconnect()

def test_with_mock_db():
    app.dependency_overrides[get_db] = override_get_db
    client = TestClient(app)
    response = client.get("/users/")
    assert response.status_code == 200
    app.dependency_overrides = {}  # Always clean up
```

### Production Dockerfile

```dockerfile
FROM python:3.11-slim
WORKDIR /code
COPY ./requirements.txt /code/requirements.txt
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
COPY ./app /code/app
CMD ["gunicorn", "app.main:app", "--workers", "4",
     "--worker-class", "uvicorn.workers.UvicornWorker", "--bind", "0.0.0.0:80"]
```

Workers: 1 per CPU core on a single server. For Kubernetes: 1 worker per container, scale at cluster level.

### Health Check Endpoints

```python
@app.get("/health")
async def health_check():
    """Liveness: is the process running?"""
    return {"status": "healthy"}

@app.get("/ready")
async def readiness_check():
    """Readiness: can it handle traffic?"""
    try:
        await database.execute("SELECT 1")
        return {"status": "ready", "database": "ok"}
    except Exception as e:
        raise HTTPException(status_code=503, detail=str(e))
```

### Performance Quick Wins

```python
# Connection pooling: 2-3x throughput improvement
database = Database("postgresql://user:pass@localhost/db", min_size=5, max_size=20)

# Response caching: 90%+ database load reduction
@cache(expire=60)
async def get_user(user_id: int):
    return await db.fetch_one(...)

# Async middleware: 10-30% latency reduction
@app.middleware("http")
async def async_middleware(request, call_next):
    return await call_next(request)  # NOT blocking def
```

### Common Pitfall: Blocking Inside Async

```python
# WRONG: blocks the event loop
@app.get("/data")
async def get_data():
    response = requests.get("https://api.com")  # Blocks!

# CORRECT: use async library
import httpx
@app.get("/data")
async def get_data():
    async with httpx.AsyncClient() as client:
        response = await client.get("https://api.com")
```

## Tools & References

- [FastAPI Official Docs](https://fastapi.tiangolo.com/)
- [Pydantic v2 Migration Guide](https://docs.pydantic.dev/latest/migration/)
- `pip install fastapi[all]` - FastAPI with all optional dependencies
- `pip install pytest anyio httpx` - async testing stack

---
*Published by [MidOS](https://midos.dev) — MCP Community Library*

---
> Source: [dqikfox/mcp](https://github.com/dqikfox/mcp) — distributed by [TomeVault](https://tomevault.io).
<!-- tomevault:4.0:skill_md:2026-06-15 -->

