agentleFS
FmarzochiFmarzochi/EGC/skills/backend/fastapi-patterns/SKILL.md

fastapi-patterns

Skill61 starsChanged 56 days agoApache-2.0View on GitHub

FastAPI patterns for async APIs, dependency injection, Pydantic request and response models, OpenAPI docs, tests, security, and production readiness.

19 other projects use a version of this.

Adopted by

Most recently taken up by

Used with

Projects with it also use typescript-patterns and react-testing.

Other files people use alongside this one.

Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
  • cohen-lielSkill

    typescript-patterns

    TypeScript best practices and patterns. Use when writing TypeScript code, defining types, working with generics, or converting JavaScript to TypeScript.

    Also in 3 projects that use this

  • SentrySkill

    react-testing

    Write and review React/TypeScript tests for Sentry's frontend using Jest and React Testing Library. Use when adding or editing tests in static/ (*.spec.tsx), writing component/hook tests, mocking API responses with MockApiClient, testing routing or network requests, or when asked to "write a frontend test", "add a React test", "test this component", or "fix a flaky RTL test".

    Also in 3 projects that use this

The file

Show its rules and text

We split this skill into the rules it gives an agent

18 rules. 0 are common, 15 are distinctive, and 3 are very specific to this project.

  • 0

    Common

    Most of the 13 edited versions we read keep these.

  • 15

    Distinctive

    This author's own choices. Worth a look.

  • 3

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Treat the FastAPI app as a thin HTTP layer over explicit dependencies and service code:

    line 21
  2. Very specific:

    Prefer small routers and explicit response_model declarations.

    line 29
  3. Distinctive:

    Use a factory so tests and workers can build the app with controlled settings.

    line 56
  4. Very specific:

    Do not use allow_origins=["*"] with allow_credentials=True.

    line 101
  5. Distinctive:

    Keep request, update, and response models separate.

    line 105
  6. Distinctive:

    Use dependency injection for request-scoped resources.

    line 141
  7. Distinctive:

    Avoid creating sessions, clients, or credentials inline inside route handlers.

    line 181
  8. Distinctive:

    Keep route handlers async when they perform I/O, and use async libraries inside them.

    line 185
  9. Very specific:

    Use httpx.AsyncClient for external HTTP calls from async handlers.

    line 213
  10. Distinctive:

    Keep CORS origins environment-specific.

    line 296
  11. Distinctive:

    Put rate limits on auth and write-heavy endpoints.

    line 297
  12. Distinctive:

    Use Pydantic models for all request bodies.

    line 298
  13. Distinctive:

    Use ORM parameter binding or SQLAlchemy Core expressions.

    line 299
  14. Distinctive:

    Run dependency audit tooling in CI.

    line 301
  15. Distinctive:

    Add pagination to list endpoints.

    line 306
  16. Distinctive:

    Use async HTTP/database clients in async paths.

    line 308
  17. Distinctive:

    Add compression only after checking payload size and CPU tradeoffs.

    line 309
  18. Distinctive:

    Use these examples as patterns, not as project-wide templates:

    line 314

“Common” is measured: kept by at least 6 in 10 of the 13 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

FastAPI Patterns

Production-oriented patterns for FastAPI services.

When to Use

  • Building or reviewing a FastAPI app.
  • Splitting routers, schemas, dependencies, and database access.
  • Writing async endpoints that call a database or external service.
  • Adding authentication, authorization, OpenAPI docs, tests, or deployment settings.
  • Checking a FastAPI PR for copy-pasteable examples and production risks.

How It Works

Treat the FastAPI app as a thin HTTP layer over explicit dependencies and service code:

  • main.py owns app construction, middleware, exception handlers, and router registration.
  • schemas/ owns Pydantic request and response models.
  • dependencies.py owns database, auth, pagination, and request-scoped dependencies.
  • services/ or crud/ owns business and persistence operations.
  • tests/ overrides dependencies instead of opening production resources.

Prefer small routers and explicit response_model declarations. Keep raw ORM objects, secrets, and framework globals out of response schemas.

Project Layout

app/
|-- main.py
|-- config.py
|-- dependencies.py
|-- exceptions.py
|-- api/
|   `-- routes/
|       |-- users.py
|       `-- health.py
|-- core/
|   |-- security.py
|   `-- middleware.py
|-- db/
|   |-- session.py
|   `-- crud.py
|-- models/
|-- schemas/
`-- tests/

Application Factory

Use a factory so tests and workers can build the app with controlled settings.

from contextlib import asynccontextmanager

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

from app.api.routes import health, users
from app.config import settings
from app.db.session import close_db, init_db
from app.exceptions import register_exception_handlers


@asynccontextmanager
async def lifespan(app: FastAPI):
    await init_db()
    yield
    await close_db()


def create_app() -> FastAPI:
    app = FastAPI(
        title=settings.api_title,
        version=settings.api_version,
        lifespan=lifespan,
    )

    app.add_middleware(
        CORSMiddleware,
        allow_origins=settings.cors_origins,
        allow_credentials=bool(settings.cors_origins),
        allow_methods=["GET", "POST", "PUT", "PATCH", "DELETE"],
        allow_headers=["Authorization", "Content-Type"],
    )

    register_exception_handlers(app)
    app.include_router(health.router, prefix="/health", tags=["health"])
    app.include_router(users.router, prefix="/api/v1/users", tags=["users"])
    return app


app = create_app()

Do not use allow_origins=["*"] with allow_credentials=True; browsers reject that combination and Starlette disallows it for credentialed requests.

Pydantic Schemas

Keep request, update, and response models separate.

from datetime import datetime
from typing import Annotated
from uuid import UUID

from pydantic import BaseModel, ConfigDict, EmailStr, Field


class UserBase(BaseModel):
    email: EmailStr
    full_name: Annotated[str, Field(min_length=1, max_length=100)]


class UserCreate(UserBase):
    password: Annotated[str, Field(min_length=12, max_length=128)]


class UserUpdate(BaseModel):
    email: EmailStr | None = None
    full_name: Annotated[str | None, Field(min_length=1, max_length=100)] = None


class UserResponse(UserBase):
    model_config = ConfigDict(from_attributes=True)

    id: UUID
    created_at: datetime
    updated_at: datetime

Response models must never include password hashes, access tokens, refresh tokens, or internal authorization state.

Dependencies

Use dependency injection for request-scoped resources.

from collections.abc import AsyncIterator
from uuid import UUID

from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from sqlalchemy.ext.asyncio import AsyncSession

from app.core.security import decode_token
from app.db.session import session_factory
from app.models.user import User


oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/login")


async def get_db() -> AsyncIterator[AsyncSession]:
    async with session_factory() as session:
        try:
            yield session
            await session.commit()
        except Exception:
            await session.rollback()
            raise


async def get_current_user(
    token: str = Depends(oauth2_scheme),
    db: AsyncSession = Depends(get_db),
) -> User:
    payload = decode_token(token)
    user_id = UUID(payload["sub"])
    user = await db.get(User, user_id)
    if user is None:
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token")
    return user

Avoid creating sessions, clients, or credentials inline inside route handlers.

Async Endpoints

Keep route handlers async when they perform I/O, and use async libraries inside them.

from fastapi import APIRouter, Depends, Query
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from app.dependencies import get_current_user, get_db
from app.models.user import User
from app.schemas.user import UserResponse


router = APIRouter()


@router.get("/", response_model=list[UserResponse])
async def list_users(
    limit: int = Query(default=50, ge=1, le=100),
    offset: int = Query(default=0, ge=0),
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    result = await db.execute(
        select(User).order_by(User.created_at.desc()).limit(limit).offset(offset)
    )
    return result.scalars().all()

Use httpx.AsyncClient for external HTTP calls from async handlers. Do not call requests in an async route.

Error Handling

Centralize domain exceptions and keep response shapes stable.

from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse


class ApiError(Exception):
    def __init__(self, status_code: int, code: str, message: str):
        self.status_code = status_code
        self.code = code
        self.message = message


def register_exception_handlers(app: FastAPI) -> None:
    @app.exception_handler(ApiError)
    async def api_error_handler(request: Request, exc: ApiError):
        return JSONResponse(
            status_code=exc.status_code,
            content={"error": {"code": exc.code, "message": exc.message}},
        )

OpenAPI Customization

Assign the custom OpenAPI callable to app.openapi; do not just call the function once.

from fastapi import FastAPI
from fastapi.openapi.utils import get_openapi


def install_openapi(app: FastAPI) -> None:
    def custom_openapi():
        if app.openapi_schema:
            return app.openapi_schema
        app.openapi_schema = get_openapi(
            title="Service API",
            version="1.0.0",
            routes=app.routes,
        )
        return app.openapi_schema

    app.openapi = custom_openapi

Testing

Override the dependency used by Depends, not an internal helper that route handlers never reference.

import pytest
from httpx import ASGITransport, AsyncClient
from sqlalchemy.ext.asyncio import AsyncSession

from app.dependencies import get_db
from app.main import create_app


@pytest.fixture
async def client(test_session: AsyncSession):
    app = create_app()

    async def override_get_db():
        yield test_session

    app.dependency_overrides[get_db] = override_get_db
    async with AsyncClient(
        transport=ASGITransport(app=app),
        base_url="http://test",
    ) as test_client:
        yield test_client
    app.dependency_overrides.clear()

Security Checklist

  • Hash passwords with argon2-cffi, bcrypt, or a current passlib-compatible hasher.
  • Validate JWT issuer, audience, expiry, and signing algorithm.
  • Keep CORS origins environment-specific.
  • Put rate limits on auth and write-heavy endpoints.
  • Use Pydantic models for all request bodies.
  • Use ORM parameter binding or SQLAlchemy Core expressions; never build SQL with f-strings.
  • Redact tokens, authorization headers, cookies, and passwords from logs.
  • Run dependency audit tooling in CI.

Performance Checklist

  • Configure database connection pooling explicitly.
  • Add pagination to list endpoints.
  • Watch for N+1 queries and use eager loading intentionally.
  • Use async HTTP/database clients in async paths.
  • Add compression only after checking payload size and CPU tradeoffs.
  • Cache stable expensive reads behind explicit invalidation.

Examples

Use these examples as patterns, not as project-wide templates:

  • Application factory: configure middleware and routers once in create_app.
  • Schema split: UserCreate, UserUpdate, and UserResponse have different responsibilities.
  • Dependency override: tests override get_db directly.
  • OpenAPI customization: assign app.openapi = custom_openapi.

See Also

  • Agent: fastapi-reviewer
  • Command: /fastapi-review
  • Skill: python-patterns
  • Skill: python-testing
  • Skill: api-design

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.