fastapi-patterns
Skill0 starsChanged 7 months agoMITView on GitHub
Best practices and patterns for building FastAPI applications. Use when creating new endpoints, structuring a FastAPI project, designing Pydantic models, handling errors, setting up authentication, or reviewing existing FastAPI code for quality.
- Reads credentials
19 other projects use a version of this, 8 of them edited.
Adopted by
Most recently taken up by
- skateddu/claude-code-python-setuptheir own version, first seen Nov 8, 2026
- ahmadvahidi/skill-atlasedited, first seen Nov 8, 2026
- ControllableModel/SWE-Skills-Benchtheir own version, first seen Nov 8, 2026
- ku5ic/claude-kittheir own version, first seen Nov 4, 2026
- ai-enhanced-engineer/aiee-skillstheir own version, first seen Nov 1, 2026
- renepf/claude-skillsedited, first seen Oct 25, 2026
Used with
Projects with it also use redis-patterns, typescript-patterns and 10 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.
nek1987Skillredis-patterns
Upstash Redis patterns for caching and rate limiting.
Also in 6 projects that use this
cohen-lielSkilltypescript-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
LangChainSkilldocker-patterns
Best practices for Docker containerization and multi-stage builds
Also in 7 projects that use this
mstuder-slalomSkillgolang-patterns
Idiomatic Go patterns, best practices, and conventions for building robust, efficient, and maintainable Go applications.
Also in 5 projects that use this
mstuder-slalomSkilldjango-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
xenitV1Skillpython-patterns
Python development principles and decision-making. Framework selection, async patterns, type hints, project structure. Teaches thinking, not copying.
Also in 6 projects that use this
- +1 more from mstuder-slalom
Other versions19 projects use a version of this.
Word for word · 0
None found.
Edited · 8
- gongyijie85/dsh-ecc★ 811% same
- peopleforrester/claude-dotfiles★ 310% same
- NISTALTALSON/essential-claude-skills★ 111% same
- productionx-in/Core-Skills★ 111% same
- SamuelNDCE/claude-super-skill-library★ 111% same
- ahmadvahidi/skill-atlas★ 011% same
- imMamdouhaboammar/tiktok-growth-automation-mesh★ 011% same
- renepf/claude-skills★ 011% same
Wrote their own version · 11
- Fmarzochi/EGC★ 61same name
- skateddu/claude-code-python-setup★ 28same name
- ControllableModel/SWE-Skills-Bench★ 8same name
- hieutrtr/ai1-skills★ 8same name
- ai-enhanced-engineer/aiee-skills★ 4same name
- bjornjee/skills★ 2same name
- ku5ic/claude-kit★ 2same name
- ronmkr/PromptBook★ 2same name
- seikaikyo/dash-skills★ 2same name
- quruhao4800/agent-workflow★ 1same name
1 more not listed
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository8 other files it gives its agents.
- app-store-preflight-skillsSkill
- frontend-designSkill
- mcp-builderSkill
- code-documentationSkill
- python-testingSkill
- ui-patternsSkill
- view-refactorSkill
- theme-factorySkill
The file
We split this skill into the rules it gives an agent
2 rules. 0 are common, 1 is distinctive, and 1 is very specific to this project.
0
Common
Most of the 15 edited versions we read keep these.
1
Distinctive
This author's own choices. Worth a look.
1
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 295
Use background tasks for fire-and-forget work (emails, webhooks).
- Very specific: line 354
Pydantic v2 uses
model_config: not inner Config class — don't mix patterns
“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.
Files
FastAPI Patterns Skill
Build FastAPI applications that are well-structured, type-safe, and easy to maintain.
Project Structure
For non-trivial APIs, use this layout:
app/
├── main.py # App factory, lifespan, global middleware
├── api/
│ ├── deps.py # Shared dependencies (get_db, get_current_user)
│ └── v1/
│ ├── router.py # Aggregates all v1 routers
│ ├── users.py # /users endpoints
│ └── items.py # /items endpoints
├── models/
│ ├── user.py # SQLAlchemy / SQLModel ORM models
│ └── item.py
├── schemas/
│ ├── user.py # Pydantic request/response schemas
│ └── item.py
├── services/
│ ├── user_service.py # Business logic
│ └── item_service.py
├── core/
│ ├── config.py # Settings via pydantic-settings
│ ├── security.py # Password hashing, JWT helpers
│ └── database.py # Engine, SessionLocal, Base
└── tests/App Factory and Lifespan
# main.py
from contextlib import asynccontextmanager
from fastapi import FastAPI
from app.api.v1.router import api_router
from app.core.database import engine, Base
@asynccontextmanager
async def lifespan(app: FastAPI):
# Startup
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
yield
# Shutdown
await engine.dispose()
def create_app() -> FastAPI:
app = FastAPI(
title="My API",
version="1.0.0",
lifespan=lifespan,
)
app.include_router(api_router, prefix="/api/v1")
return app
app = create_app()Pydantic Schemas
Separate request schemas, response schemas, and DB models. Never return ORM models directly.
# schemas/user.py
from pydantic import BaseModel, EmailStr, ConfigDict
from datetime import datetime
class UserCreate(BaseModel):
email: EmailStr
password: str
name: str
class UserUpdate(BaseModel):
name: str | None = None
email: EmailStr | None = None
class UserResponse(BaseModel):
model_config = ConfigDict(from_attributes=True) # enables ORM mode
id: int
email: EmailStr
name: str
created_at: datetime
# Never include: password, password_hashUse model_config = ConfigDict(from_attributes=True) on response schemas
This lets FastAPI serialize ORM objects directly: UserResponse.model_validate(db_user).
Dependency Injection
# api/deps.py
from typing import Annotated
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.database import get_async_session
from app.core.security import decode_token
from app.models.user import User
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/token")
async def get_db() -> AsyncSession:
async with get_async_session() as session:
yield session
async def get_current_user(
token: Annotated[str, Depends(oauth2_scheme)],
db: Annotated[AsyncSession, Depends(get_db)],
) -> User:
payload = decode_token(token)
if not payload:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
user = await db.get(User, payload["sub"])
if not user:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
return user
# Typed aliases — use these in route signatures
CurrentUser = Annotated[User, Depends(get_current_user)]
DbSession = Annotated[AsyncSession, Depends(get_db)]Router and Route Patterns
# api/v1/users.py
from typing import Annotated
from fastapi import APIRouter, HTTPException, status
from app.api.deps import CurrentUser, DbSession
from app.schemas.user import UserCreate, UserResponse, UserUpdate
from app.services.user_service import UserService
router = APIRouter(prefix="/users", tags=["users"])
@router.get("/me", response_model=UserResponse)
async def get_current_user_profile(current_user: CurrentUser):
return current_user
@router.get("/{user_id}", response_model=UserResponse)
async def get_user(user_id: int, db: DbSession):
user = await UserService(db).get_by_id(user_id)
if not user:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
return user
@router.post("/", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
async def create_user(payload: UserCreate, db: DbSession):
return await UserService(db).create(payload)
@router.patch("/{user_id}", response_model=UserResponse)
async def update_user(
user_id: int,
payload: UserUpdate,
current_user: CurrentUser,
db: DbSession,
):
if current_user.id != user_id:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
return await UserService(db).update(user_id, payload)Error Handling
Use HTTPException for expected errors
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="User not found",
)Custom exception handlers for domain errors
# main.py
from fastapi import Request
from fastapi.responses import JSONResponse
class ResourceNotFoundError(Exception):
def __init__(self, resource: str, id: int):
self.resource = resource
self.id = id
@app.exception_handler(ResourceNotFoundError)
async def not_found_handler(request: Request, exc: ResourceNotFoundError):
return JSONResponse(
status_code=404,
content={"detail": f"{exc.resource} with id {exc.id} not found"},
)Validation error format
FastAPI automatically returns 422 with field-level detail for Pydantic validation errors. Don't re-raise or wrap these.
Settings
# core/config.py
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8")
database_url: str
secret_key: str
access_token_expire_minutes: int = 30
debug: bool = False
settings = Settings() # reads from env / .env fileAuthentication (JWT)
# core/security.py
from datetime import datetime, timedelta, timezone
from jose import JWTError, jwt
from passlib.context import CryptContext
from app.core.config import settings
pwd_context = CryptContext(schemes=["bcrypt"])
def hash_password(password: str) -> str:
return pwd_context.hash(password)
def verify_password(plain: str, hashed: str) -> bool:
return pwd_context.verify(plain, hashed)
def create_access_token(subject: int | str) -> str:
expire = datetime.now(timezone.utc) + timedelta(minutes=settings.access_token_expire_minutes)
return jwt.encode({"sub": str(subject), "exp": expire}, settings.secret_key, algorithm="HS256")
def decode_token(token: str) -> dict | None:
try:
return jwt.decode(token, settings.secret_key, algorithms=["HS256"])
except JWTError:
return NoneBackground Tasks
from fastapi import BackgroundTasks
@router.post("/orders/", status_code=201)
async def create_order(
payload: OrderCreate,
background_tasks: BackgroundTasks,
db: DbSession,
):
order = await OrderService(db).create(payload)
background_tasks.add_task(send_order_confirmation_email, order.id)
return orderUse background tasks for fire-and-forget work (emails, webhooks). For anything that must complete reliably, use a task queue (Celery, ARQ, or similar).
Middleware
from fastapi.middleware.cors import CORSMiddleware
from fastapi.middleware.gzip import GZipMiddleware
import time
app.add_middleware(
CORSMiddleware,
allow_origins=settings.allowed_origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
app.add_middleware(GZipMiddleware, minimum_size=1000)
@app.middleware("http")
async def add_request_timing(request, call_next):
start = time.perf_counter()
response = await call_next(request)
response.headers["X-Process-Time"] = str(time.perf_counter() - start)
return responseOpenAPI Customization
@router.post(
"/items/",
response_model=ItemResponse,
status_code=201,
summary="Create a new item",
response_description="The created item",
responses={
409: {"description": "Item with this name already exists"},
},
)
async def create_item(payload: ItemCreate, db: DbSession):
...Tag routers to group endpoints in the docs:
router = APIRouter(prefix="/items", tags=["items"])Common Gotchas
- Never use
async defwith blocking I/O — userun_in_executoror switch to a sync route if the library isn't async-native - Dependency functions that
yieldmust be async generators for async DB sessions —async def get_db()withyield response_modelstrips fields not in the schema — use it as security, not just docs- Pydantic v2 uses
model_confignot innerConfigclass — don't mix patterns - Avoid returning raw dicts — always use
response_modelso the schema is enforced and documented
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
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.

