FastAPI Skill
Canonical guide for building and documenting FastAPI APIs.
This file consolidates the previous core.md and api-endpoints.md content into one entrypoint to reduce duplication.
Scope
- •App factory and lifespan setup
- •Router organization and versioning
- •Dependency injection patterns
- •Endpoint handler contracts
- •Request/response schemas (Pydantic)
- •Pydantic settings/config patterns (
pydantic-settings) - •Event-driven messaging with FastStream
- •Async/service/repository boundaries
- •API endpoint documentation with Markdown tables
- •Pointers to security, rate limiting, and SQLModel companions
Companion Files
Load these when needed:
- •
.claude/skills/fastapi/security.md - •
.claude/skills/fastapi/rate-limiting.md - •
.claude/skills/fastapi/sqlmodel.md - •
.claude/skills/fastapi/pydantic.md - •
.claude/skills/fastapi/faststream.md
Implementation Standard
1) App Factory + Lifespan
- •Initialize heavy resources in lifespan, not import time.
- •Use
create_app()as entrypoint. - •Include routers from one composition module.
python
from contextlib import asynccontextmanager
from fastapi import FastAPI
@asynccontextmanager
async def lifespan(app: FastAPI):
# startup resources
yield
# shutdown cleanup
def create_app() -> FastAPI:
app = FastAPI(title="My API", version="v1", lifespan=lifespan)
app.include_router(api_router)
return app
2) Router Organization
- •One router file per domain (
auth,posts,projects, etc.). - •Version prefix at composition level (
/api/v1). - •Tags by domain.
python
api_router = APIRouter(prefix="/api/v1") api_router.include_router(auth.router, prefix="/auth", tags=["auth"]) api_router.include_router(posts.router, prefix="/posts", tags=["posts"])
3) Endpoint Handler Contract
Every endpoint should define:
- •
response_model - •
status_code - •
summary - •explicit dependencies for auth/permissions when needed
Keep handlers thin:
- •parse + validate input
- •call service/use-case
- •map expected errors (
HTTPException)
4) Schema Contract (Pydantic)
- •Use dedicated request/response schemas.
- •Avoid returning ORM/entity objects directly.
- •Use
model_config = ConfigDict(from_attributes=True)in response models. - •For partial update, use optional fields and
exclude_unset=True.
5) Pydantic + pydantic-settings Best Practices
Use this section as the canonical contract for validation and configuration models.
For a full deep-dive, load .claude/skills/fastapi/pydantic.md.
Install Notes
- •
pydantic-settingsforBaseSettings. - •
pydantic[email](oremail-validator) when usingEmailStr.
Schema Model Rules
- •Separate schemas by purpose:
Create,Update,Out. - •Use strict model config by default:
- •
extra="forbid" - •
str_strip_whitespace=True - •
validate_assignment=True
- •
- •Use
from_attributes=Truefor response schemas that read ORM objects. - •Use
model_dump(exclude_unset=True)for PATCH/partial updates.
python
from pydantic import BaseModel, ConfigDict, EmailStr, SecretStr, field_validator
class UserCreate(BaseModel):
model_config = ConfigDict(
extra="forbid",
str_strip_whitespace=True,
validate_assignment=True,
)
email: EmailStr
username: str
password: SecretStr
@field_validator("username")
@classmethod
def username_min_len(cls, value: str) -> str:
if len(value) < 3:
raise ValueError("username must have at least 3 characters")
return value
Settings Rules (pydantic-settings)
- •Keep all runtime configuration in one
Settingsmodel. - •Use env prefix and
.envfile. - •Use typed fields (
PostgresDsn,AnyHttpUrl,EmailStr,SecretStr). - •Never hardcode secrets in source.
- •Load settings once with cache.
python
from functools import lru_cache
from pydantic import AnyHttpUrl, EmailStr, PostgresDsn, SecretStr
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
model_config = SettingsConfigDict(
env_file=".env",
env_prefix="APP_",
extra="ignore",
)
app_name: str = "my-api"
debug: bool = False
api_base_url: AnyHttpUrl
database_url: PostgresDsn
support_email: EmailStr
secret_key: SecretStr
@lru_cache
def get_settings() -> Settings:
return Settings()
FastAPI dependency pattern:
python
from fastapi import Depends
def get_config(settings: Settings = Depends(get_settings)) -> Settings:
return settings
6) Async and Boundaries
- •Use async I/O end-to-end for DB/network operations.
- •Keep business logic in services, not routers.
- •Keep repositories/data access isolated from API layer.
7) Error Contract
- •
400/422for invalid input - •
401/403for auth/authz failures - •
404for missing resources - •
409for conflict/duplicate state - •Keep
detailmessages stable and actionable
Endpoint Documentation Standard (Tables)
For each domain, maintain a Markdown table with this contract.
Required Columns
- •
Method - •
Path - •
Summary - •
Auth - •
Request - •
Response - •
Errors
Optional Columns
- •
Rate Limit - •
Idempotent - •
Notes
Table Template
md
## <Domain> API
| Method | Path | Summary | Auth | Request | Response | Errors |
| ------ | ----------------------- | ------------------ | -------- | ---------------------- | ------------------------- | ------------------- |
| GET | /api/v1/<resource> | List resources | public | query: `ListQuery` | `200` `list[ResourceOut]` | `400` |
| GET | /api/v1/<resource>/{id} | Get resource by id | public | path: `id:int` | `200` `ResourceOut` | `404` |
| POST | /api/v1/<resource> | Create resource | required | body: `ResourceCreate` | `201` `ResourceOut` | `400`, `409`, `422` |
| PUT | /api/v1/<resource>/{id} | Update resource | required | body: `ResourceUpdate` | `200` `ResourceOut` | `404`, `422` |
| DELETE | /api/v1/<resource>/{id} | Delete resource | required | path: `id:int` | `204` none | `404` |
Endpoint Template
python
@router.get(
"/{resource_id}",
response_model=ResourceOut,
status_code=200,
summary="Get resource by id",
)
async def get_resource(resource_id: int) -> ResourceOut:
resource = await service.get(resource_id)
if resource is None:
raise HTTPException(status_code=404, detail="Resource not found")
return resource
Guardrails
- •Keep business logic out of routers.
- •Keep endpoint tables synchronized with implementation changes.
- •Prefer explicit contracts over implicit behavior.
- •Keep local API behavior aligned with test coverage.