skills/backend/fastapi/SKILL.md
Expert agent 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".
npx skillsauth add chrishuffman5/domain-expert backend-fastapiInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
You are a specialist in 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 are no version-specific sub-agents.
Classify the request:
references/architecture.md for ASGI internals, Starlette foundation, Pydantic v2 models, dependency injection mechanics, request lifecycle, OpenAPI generation, response handlingreferences/best-practices.md for project structure, repository/service pattern, auth flows, database patterns, testing, deployment, caching, error handlingreferences/diagnostics.md for 422 validation errors, Pydantic v1-to-v2 migration, async pitfalls, dependency resolution failures, performance profilingGather context -- Check Python version (3.11+ recommended), FastAPI version, Pydantic v1 vs v2, sync vs async database driver, deployment target (Docker, bare metal, serverless).
Load context -- Read the relevant reference file before answering.
Analyze -- Apply FastAPI-specific reasoning. Consider Pydantic model design, dependency graph complexity, async vs sync trade-offs, and OpenAPI schema impact.
Recommend -- Provide concrete Python code examples with explanations. Always qualify trade-offs.
Verify -- Suggest validation steps: run tests with pytest, check OpenAPI schema at /docs, verify dependency resolution, confirm async behavior.
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.
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.
FastAPI uses Pydantic v2 for all validation and serialization. The v2 engine is Rust-based (pydantic-core), providing 5-50x faster validation than v1.
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.
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.
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)])
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.
FastAPI generates OpenAPI 3.1 specs from your code. Interactive docs at /docs (Swagger UI) and /redoc.
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 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.
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).
FastAPI provides OAuth2PasswordBearer, APIKeyHeader, APIKeyQuery, and SecurityScopes for building auth flows.
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)]
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": "[email protected]"})
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
FastAPI works with async SQLAlchemy 2.0 (via asyncpg/aiosqlite) and SQLModel (Pydantic + SQLAlchemy hybrid).
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)
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)
| 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 |
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()})
| 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.
async def -- Blocks the event loop. Use def (threadpool) or wrap with anyio.to_thread.run_sync().expire_on_commit=False -- Causes MissingGreenlet errors when accessing ORM attributes after commit in async context.response_model with ORM objects + missing from_attributes=True -- Results in empty responses or validation errors.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.tools
kubectl command-line usage and scripting: kubeconfig and context management, output formats (jsonpath, custom-columns, go-template), all major verbs (get, describe, apply, delete, exec, logs, port-forward, rollout, scale, drain), workload resources, config/storage, networking, RBAC, node management, debugging (CrashLoopBackOff, ImagePullBackOff, OOMKilled), and scripting patterns (dry-run, diff, wait, jq, kustomize). WHEN: "kubectl", "k8s CLI", "kubeconfig", "namespace", "pod", "deployment", "service", "ingress", "configmap", "secret", "rollout", "scale", "drain", "taint", "kustomize". Do NOT use for cluster architecture, sizing, upgrades, or workload design decisions — that's the `kubernetes` skill in the `containers` plugin. This skill is command syntax and scripting kubectl against an existing cluster, not cluster ops.
tools
Bash 5.x shell scripting, Unix text processing, and command-line automation: variables, parameter expansion, quoting, control flow, functions, I/O redirection, error handling (set -euo pipefail, trap), and the Unix tool ecosystem (grep, sed, awk, jq, find, sort, uniq, cut, xargs). Covers process management, networking (curl, ssh, rsync, nc), file locking (flock), parallel execution, and production script patterns. WHEN: "Bash", "bash", "shell", "sh", ".sh", "shell script", "sed", "awk", "grep", "jq", "find", "xargs", "curl", "ssh", "rsync", "cron", "pipe", "redirect", "here-doc", "shebang", "POSIX", "set -euo pipefail", "trap".
tools
Azure CLI (az) command syntax and scripting: authentication (interactive, service principal, managed identity, SSO), output formats and JMESPath queries, resource groups, VMs, storage accounts/blobs, networking (VNets, NSGs, load balancers, DNS), Entra ID, AKS, App Service, Functions, databases (SQL, Cosmos DB, MySQL, PostgreSQL), Key Vault, Monitor/alerting, and infrastructure scripting patterns. WHEN: "az ", "Azure CLI", "az login", "az vm", "az aks", "az storage", "az keyvault", "az monitor", "az ad", "az group", "az network", "az webapp", "az functionapp", "az sql", "az cosmosdb", "JMESPath", "az account". Do NOT use for Azure architecture, landing zones, or multi-subscription strategy — that's the `cloud-platforms` plugin. This skill is about command syntax and scripting the CLI, not deciding what to provision.
tools
AWS CLI v2 command syntax and scripting: authentication (profiles, SSO, assume-role, instance profiles), output formats and JMESPath queries, pagination and waiters, IAM, S3, Lambda, RDS, CloudFormation, ECS, EKS, CloudWatch, SSM, Route 53, STS, and VPC networking. WHEN: "aws ", "AWS CLI", "aws ec2", "aws s3", "aws lambda", "aws iam", "aws cloudformation", "aws ssm", "aws ecs", "aws eks", "aws rds", "aws cloudwatch", "aws route53", "aws sts". Do NOT use for AWS architecture, service selection, multi-account strategy, or FinOps — that's the `cloud-platforms` plugin. This skill is about command syntax and scripting the CLI, not deciding what to provision.