agentleFS
quruhao4800quruhao4800/agent-workflow/skills/languages/fastapi-patterns/SKILL.md

fastapi-patterns

Skill1 starsChanged 6 months agoMITView on GitHub

FastAPI architecture patterns: router organization, Pydantic models, dependency injection, exception handling, and async patterns. Use for FastAPI backend work.

19 other projects use a version of this.

Adopted by

Most recently taken up by

Used with

Projects with it also use typescript-patterns, docker-patterns and 8 more.

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

  • LangChainSkill

    docker-patterns

    Best practices for Docker containerization and multi-stage builds

    Also in 7 projects that use this

  • mstuder-slalomSkill

    golang-patterns

    Idiomatic Go patterns, best practices, and conventions for building robust, efficient, and maintainable Go applications.

    Also in 5 projects that use this

  • mstuder-slalomSkill

    django-security

    Django security best practices, authentication, authorization, CSRF protection, SQL injection prevention, XSS prevention, and secure deployment configurations.

    Also in 4 projects that use this

  • nek1987Skill

    postgres-patterns

    PostgreSQL patterns for reviewing migrations and writing efficient queries. Use when reviewing Alembic migrations, optimizing queries, or debugging database issues.

    Also in 5 projects that use this

  • royaluniondesign-sysSkill

    deployment-patterns

    Deployment workflows, CI/CD pipeline patterns, Docker containerization, health checks, rollback strategies, and production readiness checklists for web applications.

    Also in 4 projects that use this

  • +1 more from mstuder-slalom

The file

We split this skill into the rules it gives an agent

4 rules. 0 are common, 0 are distinctive, and 4 are very specific to this project.

  • 0

    Common

    Most of the 15 edited versions we read keep these.

  • 0

    Distinctive

    This author's own choices. Worth a look.

  • 4

    Very specific

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

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Use separate Pydantic BaseModel classes for request and response.

    line 39
  2. Very specific:

    Use Depends() for any shared resource: database sessions, cache clients, auth context, or external service clients.

    line 65
  3. Very specific:

    Use async def for all route functions.

    line 168
  4. Very specific:

    When wrapping a synchronous CPU-bound function, use asyncio.to_thread() to avoid blocking:

    line 184

“Common” is measured: kept by at least 6 in 10 of the 15 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 Development Patterns

Router Organization

Split routes by domain using APIRouter. Mount all routers in a single app.py / main.py.

# api/users.py
from fastapi import APIRouter, Depends
from .dependencies import get_db

router = APIRouter(prefix="/users", tags=["users"])

@router.get("/{user_id}")
async def get_user(user_id: int, db=Depends(get_db)):
    ...

# main.py
from fastapi import FastAPI
from .api import users, orders, reports

app = FastAPI()
app.include_router(users.router)
app.include_router(orders.router)
app.include_router(reports.router)

File naming: one router variable per file, file name matches the resource (users.py, orders.py).


Request & Response Models

Use separate Pydantic BaseModel classes for request and response. Do not reuse the same model for both.

from pydantic import BaseModel, Field
from typing import Optional

class CreateUserRequest(BaseModel):
    name: str = Field(..., min_length=1, max_length=100)
    email: str = Field(..., pattern=r"^[^@]+@[^@]+\.[^@]+$")
    role: str = Field(default="viewer")

class UserResponse(BaseModel):
    id: int
    name: str
    email: str
    role: str

    model_config = {"from_attributes": True}  # enables ORM mode

Naming convention: XxxRequest for input, XxxResponse for output, XxxSchema for shared internal use.


Dependency Injection

Use Depends() for any shared resource: database sessions, cache clients, auth context, or external service clients. The same pattern works for any injectable singleton.

# dependencies.py
from functools import lru_cache
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine, async_sessionmaker
import redis.asyncio as aioredis
from .config import Settings

@lru_cache
def get_settings() -> Settings:
    return Settings()

engine = create_async_engine(get_settings().database_url)
AsyncSessionLocal = async_sessionmaker(engine, expire_on_commit=False)

async def get_db() -> AsyncGenerator[AsyncSession, None]:
    async with AsyncSessionLocal() as session:
        yield session

async def get_redis() -> aioredis.Redis:
    return aioredis.from_url(get_settings().redis_url)

# The same pattern applies to any singleton client — LLM client, HTTP client, etc.:
# async def get_llm_client() -> LLMClient:
#     return LLMClient(api_key=get_settings().llm_api_key)

# Usage in route
@router.post("/orders", response_model=OrderResponse)
async def create_order(
    request: CreateOrderRequest,
    db: AsyncSession = Depends(get_db),
    cache: aioredis.Redis = Depends(get_redis),
):
    ...

Exception Handling

Define custom exceptions and register global handlers. Never return raw exception details to the client.

# exceptions.py
class AppError(Exception):
    def __init__(self, code: str, message: str, status_code: int = 400):
        self.code = code
        self.message = message
        self.status_code = status_code

class NotFoundError(AppError):
    def __init__(self, resource: str, resource_id):
        super().__init__("NOT_FOUND", f"{resource} {resource_id} not found", 404)

# main.py — register handler
from fastapi import Request
from fastapi.responses import JSONResponse

@app.exception_handler(AppError)
async def app_error_handler(request: Request, exc: AppError):
    return JSONResponse(
        status_code=exc.status_code,
        content={"code": exc.code, "message": exc.message},
    )

# In route — raise, never return error dict manually
@router.get("/{user_id}")
async def get_user(user_id: int, db=Depends(get_db)):
    user = await user_service.get(db, user_id)
    if not user:
        raise NotFoundError("User", user_id)
    return UserResponse.model_validate(user)

HTTP Status Codes

SituationCode
Successful query200
Resource created201
Successful deletion (no body)204
Business logic error400
Unauthenticated401
Insufficient permission403
Resource not found404
Request validation failed422 (FastAPI default)
Internal server error500

Declare the response model and status code explicitly on the route decorator:

@router.post("/users", response_model=UserResponse, status_code=201)
async def create_user(request: CreateUserRequest, db=Depends(get_db)):
    ...

Async Patterns

Use async def for all route functions. Use await for any I/O operation (DB, cache, HTTP calls).

# GOOD — async all the way down
@router.get("/users/{user_id}")
async def get_user(user_id: int, db: AsyncSession = Depends(get_db)):
    result = await db.execute(select(User).where(User.id == user_id))
    return result.scalar_one_or_none()

# BAD — blocking call inside async route blocks the event loop
@router.get("/users/{user_id}")
async def get_user(user_id: int):
    time.sleep(1)          # blocks event loop
    result = requests.get(...)  # sync HTTP inside async context

When wrapping a synchronous CPU-bound function, use asyncio.to_thread() to avoid blocking:

import asyncio

@router.post("/analyze")
async def analyze(payload: AnalyzeRequest):
    result = await asyncio.to_thread(heavy_cpu_function, payload.data)
    return {"result": result}

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.