Expert skill for FastAPI web framework development. Covers ASGI/Starlette foundation, Pydantic v2 integration, dependency injection, path operations, automatic OpenAPI generation, async/await patterns, middleware, security (OAuth2, JWT), testing (TestClient, httpx), SQLAlchemy/SQLModel integration, and deployment. WHEN: \"FastAPI\", \"Starlette\", \"ASGI\", \"Pydantic\", \"Depends\", \"APIRouter\", \"path operation\", \"response_model\", \"OpenAPI auto-docs\", \"Swagger UI\", \"FastAPI middle...
Pro scans all 4 files and shows the line behind each finding
Scanned 9/24/2026
npx -y skills add chrishuffman5/domain-expert --skill fastapi --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Fastapi?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/chrishuffman5-fastapi)More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.
---
name: fastapi
description: "Expert skill for FastAPI web framework development. Covers ASGI/Starlette foundation, Pydantic v2 integration, dependency injection, path operations, automatic OpenAPI generation, async/await patterns, middleware, security (OAuth2, JWT), testing (TestClient, httpx), SQLAlchemy/SQLModel integration, and deployment. WHEN: \"FastAPI\", \"Starlette\", \"ASGI\", \"Pydantic\", \"Depends\", \"APIRouter\", \"path operation\", \"response_model\", \"OpenAPI auto-docs\", \"Swagger UI\", \"FastAPI middleware\", \"FastAPI dependency injection\", \"FastAPI testing\", \"TestClient\", \"BackgroundTasks\", \"SQLModel\", \"uvicorn\", \"FastAPI WebSocket\", \"FastAPI security\", \"OAuth2PasswordBearer\". Do NOT use for general Python scripting, automation, or CLI-tool questions unrelated to the FastAPI framework — that's the `cli-scripting` plugin's `python` skill."
license: MIT
---
# FastAPI
This skill covers FastAPI development. FastAPI is a modern, high-performance Python web framework built on Starlette (ASGI) and Pydantic v2. It provides automatic OpenAPI documentation, type-driven request validation, a powerful dependency injection system, and first-class async support. FastAPI follows rolling 0.x releases -- there is no version-specific reference material.
## How to Approach Tasks
1. **Classify** the request:
- **Architecture** -- Load `references/architecture.md` for ASGI internals, Starlette foundation, Pydantic v2 models, dependency injection mechanics, request lifecycle, OpenAPI generation, response handling
- **Best practices** -- Load `references/best-practices.md` for project structure, repository/service pattern, auth flows, database patterns, testing, deployment, caching, error handling
- **Troubleshooting** -- Load `references/diagnostics.md` for 422 validation errors, Pydantic v1-to-v2 migration, async pitfalls, dependency resolution failures, performance profiling
2. **Gather context** -- Check Python version (3.11+ recommended), FastAPI version, Pydantic v1 vs v2, sync vs async database driver, deployment target (Docker, bare metal, serverless).
3. **Load context** -- Read the relevant reference file before answering.
4. **Analyze** -- Apply FastAPI-specific reasoning. Consider Pydantic model design, dependency graph complexity, async vs sync trade-offs, and OpenAPI schema impact.
5. **Recommend** -- Provide concrete Python code examples with explanations. Always qualify trade-offs.
6. **Verify** -- Suggest validation steps: run tests with `pytest`, check OpenAPI schema at `/docs`, verify dependency resolution, confirm async behavior.
## Core Architecture
### ASGI and Starlette Foundation
FastAPI is a Starlette application. Every `FastAPI()` instance inherits Starlette's ASGI interface, routing, middleware stack, and request/response classes. ASGI replaces WSGI by supporting long-lived connections (WebSockets, SSE) and native async.
```python
from fastapi import FastAPI, Request
app = FastAPI()
# Direct access to Starlette primitives
@app.get("/raw")
async def raw_access(request: Request) -> dict:
return {
"method": request.method,
"client": request.client,
"scope_type": request.scope["type"],
}
```
**Starlette classes exposed by FastAPI:** `APIRouter` (from `Router`), `Request`/`Response`, `BackgroundTasks`, `WebSocket`, `StaticFiles`, `TestClient`.
### Pydantic v2 Integration
FastAPI uses Pydantic v2 for all validation and serialization. The v2 engine is Rust-based (`pydantic-core`), providing 5-50x faster validation than v1.
```python
from pydantic import BaseModel, Field, field_validator, model_validator, ConfigDict
from typing import Annotated, Self
class UserCreate(BaseModel):
model_config = ConfigDict(str_strip_whitespace=True)
username: Annotated[str, Field(min_length=3, max_length=50, pattern=r"^[a-z0-9_]+$")]
email: str
age: Annotated[int, Field(ge=18, le=120)]
class OrderCreate(BaseModel):
quantity: int
unit_price: float
total: float
@field_validator("quantity")
@classmethod
def quantity_positive(cls, v: int) -> int:
if v <= 0:
raise ValueError("quantity must be positive")
return v
@model_validator(mode="after")
def check_total(self) -> Self:
expected = self.quantity * self.unit_price
if abs(self.total - expected) > 0.01:
raise ValueError(f"total {self.total} does not match {expected:.2f}")
return self
```
**Key v2 changes:** `model_config = ConfigDict(...)` replaces inner `Config` class. `from_attributes=True` replaces `orm_mode=True`. `model_dump()` replaces `dict()`. `Field(exclude=True)` for serialization control.
### Dependency Injection
FastAPI's DI system resolves dependencies per-request via `Depends()`. Dependencies are cached within the same request, support async, and compose into sub-dependency graphs.
```python
from fastapi import Depends, Query
from typing import Annotated
class PaginationParams:
def __init__(self, page: int = Query(1, ge=1), size: int = Query(20, ge=1, le=100)):
self.page = page
self.size = size
self.offset = (page - 1) * size
Pagination = Annotated[PaginationParams, Depends()]
# Yield dependencies for resource management (DB sessions)
async def get_db() -> AsyncGenerator[AsyncSession, None]:
async with AsyncSessionLocal() as session:
try:
yield session
await session.commit()
except Exception:
await session.rollback()
raise
DB = Annotated[AsyncSession, Depends(get_db)]
# Router-level dependencies apply to all routes
router = APIRouter(prefix="/admin", dependencies=[Depends(require_admin)])
```
### Path Operations
```python
from fastapi import APIRouter, Path, Query, Body, status
router = APIRouter(prefix="/items", tags=["items"])
@router.get("/{item_id}", response_model=ItemRead, status_code=200)
async def get_item(
item_id: Annotated[int, Path(ge=1)],
include_deleted: Annotated[bool, Query()] = False,
) -> ItemRead:
...
@router.post("/", status_code=status.HTTP_201_CREATED)
async def create_item(item: ItemCreate, db: DB) -> ItemRead:
...
```
**Parameter sources:** Path params from URL template, Query params from simple types not in the path, Body from Pydantic models, plus `Header()`, `Cookie()`, `Form()`, `File()`.
**Response models:** Use `response_model=` parameter for explicit type narrowing and field filtering, or return type annotations for cleaner signatures. `response_model` runs automatic validation; return type annotations do not.
### OpenAPI Auto-Generation
FastAPI generates OpenAPI 3.1 specs from your code. Interactive docs at `/docs` (Swagger UI) and `/redoc`.
```python
app = FastAPI(
title="My API",
version="1.0.0",
docs_url="/api/docs",
redoc_url="/api/redoc",
openapi_url="/api/openapi.json",
)
# Hide internal endpoints
@app.get("/internal/metrics", include_in_schema=False)
async def metrics(): ...
# Disable docs in production
app = FastAPI(docs_url=None, redoc_url=None)
```
### Async/Await
```python
# async def: for awaitable I/O (async DB, httpx, aiofiles)
@app.get("/async")
async def async_endpoint(db: DB) -> list[ItemRead]:
result = await db.execute(select(Item))
return result.scalars().all()
# def: for sync/blocking code -- FastAPI runs these in a threadpool automatically
@app.get("/sync")
def sync_endpoint() -> dict:
return requests.get("https://external.api/data").json()
```
**Rule:** Use `async def` with awaitable I/O. Use `def` for blocking libraries (requests, psycopg2, boto3). Never call blocking I/O inside `async def` without wrapping in a threadpool.
### Middleware
```python
from fastapi.middleware.cors import CORSMiddleware
from starlette.middleware.base import BaseHTTPMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["https://app.example.com"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Custom middleware
class RequestIDMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request, call_next):
request_id = str(uuid.uuid4())
request.state.request_id = request_id
response = await call_next(request)
response.headers["X-Request-ID"] = request_id
return response
app.add_middleware(RequestIDMiddleware)
```
For high-throughput scenarios, write pure ASGI middleware instead of `BaseHTTPMiddleware` (which buffers responses).
### Security
FastAPI provides `OAuth2PasswordBearer`, `APIKeyHeader`, `APIKeyQuery`, and `SecurityScopes` for building auth flows.
```python
from fastapi.security import OAuth2PasswordBearer
import jwt
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/token")
async def get_current_user(
token: Annotated[str, Depends(oauth2_scheme)], db: DB
) -> User:
payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
user = await db.get(User, int(payload["sub"]))
if not user:
raise HTTPException(status_code=401, detail="Invalid credentials")
return user
CurrentUser = Annotated[User, Depends(get_current_user)]
```
### Testing
```python
from fastapi.testclient import TestClient
from httpx import AsyncClient, ASGITransport
# Sync testing
client = TestClient(app)
response = client.get("/items/1")
assert response.status_code == 200
# Async testing
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client:
response = await client.post("/users/", json={"email": "test@example.com"})
assert response.status_code == 201
# Dependency overrides for isolation
app.dependency_overrides[get_current_user] = lambda: mock_user
app.dependency_overrides[get_db] = lambda: test_session
```
### Database Integration
FastAPI works with async SQLAlchemy 2.0 (via `asyncpg`/`aiosqlite`) and SQLModel (Pydantic + SQLAlchemy hybrid).
```python
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker
engine = create_async_engine("postgresql+asyncpg://...", pool_size=20)
AsyncSessionLocal = async_sessionmaker(engine, expire_on_commit=False)
```
### Lifespan Events
```python
from contextlib import asynccontextmanager
@asynccontextmanager
async def lifespan(app: FastAPI):
# Startup
app.state.http_client = httpx.AsyncClient(timeout=30.0)
yield
# Shutdown
await app.state.http_client.aclose()
app = FastAPI(lifespan=lifespan)
```
## Key Patterns
| Pattern | When to Use |
|---|---|
| `Depends()` with yield | Database sessions, HTTP clients, any resource needing cleanup |
| `Annotated[T, Depends()]` | Reusable dependency type aliases (cleaner signatures) |
| `response_model` | Filtering ORM objects, returning different type than function return |
| `APIRouter` with `prefix`/`tags` | Organizing routes by resource or domain |
| `BackgroundTasks` | Fire-and-forget tasks (email, audit log) after response |
| Sub-applications via `app.mount()` | Separate OpenAPI schemas per version |
| `include_router` with `prefix` | Single OpenAPI schema, grouped routes |
## Error Handling
```python
from fastapi import HTTPException
from fastapi.exceptions import RequestValidationError
# Raise HTTP errors
raise HTTPException(status_code=404, detail="Item not found")
# Custom exception handlers
@app.exception_handler(RequestValidationError)
async def validation_handler(request, exc):
return JSONResponse(status_code=422, content={"errors": exc.errors()})
```
## Deployment
| Configuration | Command |
|---|---|
| Development | `uvicorn app.main:app --reload --port 8000` |
| Production (single) | `uvicorn app.main:app --host 0.0.0.0 --workers 1` |
| Production (multi-core) | `gunicorn app.main:app -w 4 -k uvicorn.workers.UvicornWorker` |
| Docker | Gunicorn + UvicornWorker, non-root user, multi-stage build |
**Worker formula:** `(2 * CPU_COUNT) + 1` for I/O-bound async apps.
## Anti-Patterns
1. **Blocking I/O in `async def`** -- Blocks the event loop. Use `def` (threadpool) or wrap with `anyio.to_thread.run_sync()`.
2. **Skipping `expire_on_commit=False`** -- Causes `MissingGreenlet` errors when accessing ORM attributes after commit in async context.
3. **Global mutable state** -- Each Gunicorn worker runs a separate process. Use Redis or database for shared state.
4. **`response_model` with ORM objects + missing `from_attributes=True`** -- Results in empty responses or validation errors.
5. **Overly deep dependency chains** -- FastAPI resolves the full graph per request. Keep dependency depth under 4-5 levels.
## Reference Files
Load these for deep knowledge on specific topics:
- `references/architecture.md` -- ASGI internals, Starlette foundation, Pydantic v2 (BaseModel, Field, validators, discriminated unions), dependency injection (Depends, yield, sub-dependencies, override), request lifecycle, OpenAPI generation, response model handling. **Load when:** architecture questions, Pydantic model design, DI mechanics, OpenAPI customization.
- `references/best-practices.md` -- Project structure, repository/service pattern, auth flows (JWT, OAuth2 scopes), database patterns (async SQLAlchemy, session management, Alembic), testing (TestClient, dependency overrides, database isolation), deployment (Uvicorn, Gunicorn workers, Docker), caching, background tasks, error handling. **Load when:** "how should I structure", auth implementation, database setup, testing strategy, deployment configuration.
- `references/diagnostics.md` -- Common errors (422 validation, Pydantic v1-to-v2 migration, async pitfalls, dependency resolution), debugging techniques, performance profiling, async database issues. **Load when:** troubleshooting errors, debugging validation failures, diagnosing performance problems.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!