Back to skills
SKILL.md
Fastapi
ASecurityExpert 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...
- 4 stars
- 0 votes
- 0 copies
- 0 views
- Added September 24, 2026
Works with
Security analysis
100/100Pro scans all 4 files and shows the line behind each finding
npx -y skills add chrishuffman5/domain-expert --skill fastapi --agent claude-codeAre you the author of Fastapi?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/chrishuffman5-fastapi)---
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.
Files in this skill
- SKILL.md
- references/architecture.md
- references/best-practices.md
- references/diagnostics.md
Attribution
Comments
Loading comments…