Skilllibrary fastapi-patterns
FastAPI application specialist for route design, dependency injection, Pydantic schemas, async patterns, database integration, authentication, and testing. Triggers: 'FastAPI route', 'Pydantic model', 'Depends()', 'FastAPI middleware', 'async endpoint', 'SQLAlchemy with FastAPI', 'FastAPI testing', 'OAuth2 JWT', 'FastAPI project structure', 'Alembic migration'. Do NOT use for Django, Flask, or other Python web frameworks; for general Python scripting unrelated to APIs; or for frontend-only work.
git clone https://github.com/merceralex397-collab/skilllibrary
T=$(mktemp -d) && git clone --depth=1 https://github.com/merceralex397-collab/skilllibrary "$T" && mkdir -p ~/.claude/skills && cp -r "$T/17-external-reference-seeds/fastapi-patterns" ~/.claude/skills/merceralex397-collab-skilllibrary-fastapi-patterns && rm -rf "$T"
17-external-reference-seeds/fastapi-patterns/SKILL.mdPurpose
Domain skill for building production-grade FastAPI applications — covering project structure, route design, dependency injection, Pydantic schema patterns, async/sync execution, database integration (SQLAlchemy 2.0), authentication, middleware, and testing. Produces idiomatic FastAPI code following the framework's conventions and Python typing best practices.
When to use this skill
Use this skill when:
- scaffolding a new FastAPI project or adding routes/endpoints to an existing one
- designing Pydantic request/response schemas with validation
- implementing dependency injection chains (
) for auth, DB sessions, pagination, or shared logicDepends() - integrating SQLAlchemy 2.0 (sync or async) with Alembic migrations
- implementing authentication (OAuth2 + JWT, API key, session-based)
- writing or reviewing FastAPI tests (TestClient, httpx.AsyncClient, dependency overrides)
- adding middleware (CORS, request timing, logging, exception handlers)
- deciding between
andasync def
for route handlersdef
Do not use this skill when
- the framework is Django, Flask, Starlette (without FastAPI), or Litestar — different patterns and conventions
- the task is general Python scripting unrelated to API development
- the task is frontend-only (use
or a frontend skill)tauri-solidjs - a quick one-off data transformation or calculation — use
misc-helper - the task is specifically about BigQuery SQL — use
bigquery-skill
Operating procedure
1. Assess project state
- Check for existing project structure: does
exist? Is there aapp/main.py
orpyproject.toml
withrequirements.txt
listed?fastapi - Identify FastAPI version: v0.100+ uses Pydantic v2 (
instead of innermodel_config
class,Config
instead offrom_attributes
). Checkorm_mode
orpyproject.toml
.pip freeze - Identify database layer: SQLAlchemy 1.4/2.0, Tortoise ORM, SQLModel, or none.
- Check for existing patterns: router organization, dependency injection style, schema naming conventions.
2. Project structure (scaffold or validate)
app/ ├── main.py # FastAPI app instance, startup/shutdown, include_router ├── core/ │ ├── config.py # Settings via pydantic-settings (BaseSettings) │ ├── security.py # JWT creation/verification, password hashing │ └── database.py # Engine, SessionLocal, get_db dependency ├── models/ # SQLAlchemy ORM models │ ├── __init__.py │ ├── user.py # class User(Base): ... │ └── item.py ├── schemas/ # Pydantic request/response schemas │ ├── __init__.py │ ├── user.py # UserCreate, UserRead, UserUpdate │ └── item.py ├── routers/ # APIRouter modules grouped by domain │ ├── __init__.py │ ├── users.py # router = APIRouter(prefix="/users", tags=["users"]) │ └── items.py ├── services/ # Business logic (called by routers) │ ├── __init__.py │ ├── user_service.py │ └── item_service.py ├── dependencies.py # Shared Depends() functions (auth, pagination) ├── middleware.py # Custom middleware (timing, logging) └── exceptions.py # Custom exception classes + handlers alembic/ ├── alembic.ini ├── env.py └── versions/ tests/ ├── conftest.py # Fixtures: app, client, db_session, auth headers ├── test_users.py └── test_items.py
3. Route design
- Group routes by domain in separate
instances withAPIRouter
andprefix
.tags - Keep route handlers thin — extract business logic into service functions:
@router.post("/", response_model=UserRead, status_code=201) async def create_user( user_in: UserCreate, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_admin), ): return await user_service.create(db, user_in) - Use
to control serialization and strip internal fields from responses.response_model - Use appropriate status codes:
for creation,201
for deletion,204
for reads and updates.200
4. Pydantic schema patterns
- Separate request and response schemas:
(input) vsUserCreate
(output). Never expose password hashes or internal IDs through response schemas.UserRead - Base → Create → Read → Update hierarchy:
class UserBase(BaseModel): email: EmailStr name: str class UserCreate(UserBase): password: str class UserRead(UserBase): id: int created_at: datetime model_config = ConfigDict(from_attributes=True) # Pydantic v2 class UserUpdate(BaseModel): email: EmailStr | None = None name: str | None = None - Use
for validation:Field()
,Field(min_length=1, max_length=255)
,Field(ge=0)
.Field(pattern=r"^[a-z]+$") - Use custom validators with
(Pydantic v2) or@field_validator
(v1) for complex validation logic.@validator
5. Dependency injection
- Database session: use a generator dependency that yields a session and ensures cleanup:
async def get_db() -> AsyncGenerator[AsyncSession, None]: async with async_session_maker() as session: yield session - Authentication: chain dependencies —
depends onget_current_user
which depends on the token header:oauth2_schemeasync def get_current_user( token: str = Depends(oauth2_scheme), db: AsyncSession = Depends(get_db), ) -> User: payload = verify_token(token) user = await db.get(User, payload["sub"]) if not user: raise HTTPException(status_code=401, detail="User not found") return user - Pagination: reusable dependency for list endpoints:
def get_pagination(skip: int = Query(0, ge=0), limit: int = Query(20, ge=1, le=100)): return {"skip": skip, "limit": limit} - Nested dependencies are resolved automatically by FastAPI. Use
at any depth.Depends()
6. Async vs sync decision
- Use
when:async def- Making async I/O calls (async DB drivers like
, HTTP calls withasyncpg
, file I/O withhttpx
)aiofiles - The route is I/O bound and benefits from concurrency
- Making async I/O calls (async DB drivers like
- Use plain
when:def- Using synchronous libraries (sync SQLAlchemy,
, CPU-bound computation)requests - FastAPI automatically runs
handlers in a thread pool, so they won't block the event loopdef
- Using synchronous libraries (sync SQLAlchemy,
- NEVER use sync blocking calls inside an
handler — this blocks the entire event loop. Either useasync def
(thread pool) or usedef
/asyncio.to_thread()
.run_in_executor()
7. Database integration (SQLAlchemy 2.0)
- Async setup:
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession engine = create_async_engine("postgresql+asyncpg://...", echo=False) async_session_maker = async_sessionmaker(engine, expire_on_commit=False) - Sync setup (simpler, use when async is not needed):
from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker engine = create_engine("postgresql://...", pool_pre_ping=True) SessionLocal = sessionmaker(bind=engine, autoflush=False) - Alembic migrations:
, configurealembic init alembic
to import yourenv.py
, runBase.metadata
, thenalembic revision --autogenerate -m "description"
.alembic upgrade head - Repository pattern: encapsulate DB operations in service/repository functions. Routes should not contain raw SQL or ORM queries.
8. Authentication patterns
- OAuth2 + JWT (most common):
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/token") @router.post("/auth/token") async def login(form_data: OAuth2PasswordRequestForm = Depends(), db=Depends(get_db)): user = await authenticate_user(db, form_data.username, form_data.password) if not user: raise HTTPException(status_code=401, detail="Invalid credentials") token = create_access_token(data={"sub": str(user.id)}) return {"access_token": token, "token_type": "bearer"} - API Key auth: use
orAPIKeyHeader
fromAPIKeyQuery
.fastapi.security - Role-based access: create dependency variants like
that wrapget_current_admin
and check roles.get_current_user
9. Middleware and exception handling
- CORS: always configure explicitly:
app.add_middleware(CORSMiddleware, allow_origins=["http://localhost:3000"], allow_methods=["*"], allow_headers=["*"]) - Request timing middleware:
@app.middleware("http") async def add_timing_header(request: Request, call_next): start = time.perf_counter() response = await call_next(request) response.headers["X-Process-Time"] = str(time.perf_counter() - start) return response - Exception handlers: register custom handlers for domain exceptions:
@app.exception_handler(NotFoundError) async def not_found_handler(request: Request, exc: NotFoundError): return JSONResponse(status_code=404, content={"detail": str(exc)})
10. Testing
- Sync tests with
:TestClientfrom fastapi.testclient import TestClient client = TestClient(app) response = client.post("/users/", json={"email": "test@example.com", "name": "Test", "password": "secret"}) assert response.status_code == 201 - Async tests with
:httpx.AsyncClientasync with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client: response = await client.get("/users/1") assert response.status_code == 200 - Dependency overrides for isolating tests:
app.dependency_overrides[get_db] = lambda: test_db_session app.dependency_overrides[get_current_user] = lambda: mock_user - Always clean up overrides:
in teardown.app.dependency_overrides.clear()
Decision rules
- Thin routes, fat services: route handlers should be 5–15 lines max. Extract validation logic, DB queries, and business rules into service functions.
- Schema separation always: never reuse the same Pydantic model for both input and output. Create/Read/Update schemas prevent accidental field exposure.
only with async I/O: if you can't use an async driver, useasync def
. Mixing sync blocking calls indef
is worse than usingasync def
everywhere.def- Dependency injection over global state: never import a DB session or config at module level for use in routes. Always use
.Depends() - Pydantic v2 for new projects: use
not innermodel_config = ConfigDict(...)
. Useclass Config
notfrom_attributes=True
.orm_mode=True - Repository pattern over inline queries: routes should not contain
directly. Wrap in service/repository functions for testability and reuse.db.execute(select(...)) - Explicit response_model: always set
on routes that return data. This ensures serialization strips internal fields and generates accurate OpenAPI docs.response_model - Test with dependency overrides: never mock FastAPI internals. Use
to swap dependencies for test doubles.app.dependency_overrides
Output requirements
Every response must include the applicable sections:
— complete route handler with type annotations,Route Definition
, status code, and dependencies. Include the router registration.response_model
— PydanticSchema Pair
andCreate
(andRead
if relevant) models with field validation andUpdate
.model_config
— the full chain ofDependency Chain
functions the route uses, from leaf (DB session) to root (auth guard).Depends()
— a working test function usingTest Stub
orTestClient
with dependency overrides for the route.httpx.AsyncClient
(if schema changes are involved) — the Alembic command to generate and apply the migration.Migration Note
Anti-patterns
- Sync DB calls in
routes: calling synchronous SQLAlchemyasync def
inside ansession.execute()
handler blocks the event loop and kills concurrency. Use async SQLAlchemy or change the handler toasync def
.def - No schema validation: accepting raw
ordict
instead of Pydantic models. This skips validation, loses OpenAPI docs, and invites injection bugs.Any - Fat route handlers: putting DB queries, business logic, error handling, and response formatting all in one route function. Extract into services.
- Missing dependency injection: importing
directly in route files instead of usingSessionLocal()
. This makes testing impossible without monkeypatching.Depends(get_db) - Shared request/response models: using
as both the input and the response schema. This exposes password fields in responses.UserCreate - Catching broad exceptions:
in route handlers. Use FastAPI's exception handler system with typed exceptions.except Exception: return 500 - No CORS configuration: forgetting to add
and wondering why the frontend gets blocked. Always configure explicitly for known origins.CORSMiddleware - Hardcoded configuration: putting database URLs, secret keys, or API keys directly in code. Use
with environment variables.pydantic-settings - Testing without dependency overrides: writing tests that hit a real database or external service because dependencies weren't overridden.
Related skills
— for FastAPI routes that query BigQuery as a data sourcebigquery-skill
— for desktop frontends that consume FastAPI backendstauri-solidjs
— for quick utility tasks during API developmentmisc-helper
Failure handling
- If the FastAPI version is ambiguous, check
andpip freeze | grep fastapi
. Pydantic v1 vs v2 changes schema syntax significantly.pip freeze | grep pydantic - If async tests hang, verify the event loop policy and ensure
is configured withpytest-asyncio
inasyncio_mode = "auto"
.pyproject.toml - If dependency injection fails with circular imports, restructure: put dependencies in
, models independencies.py
, and schemas inmodels/
. Import from these modules in routers.schemas/ - If Alembic autogenerate misses changes, verify that
imports all model modules soenv.py
is fully populated before comparison.Base.metadata - If
strips fields unexpectedly, check that the Pydantic model includesresponse_model
(v2) orfrom_attributes=True
(v1) and that the field names match the ORM model attributes.orm_mode=True