Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Part of a 72-plugin marketplace with 112 AI agents and 146 skills for Claude Code development automation.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
SKILL.md
1---2name: fastapi-templates3description: Create production-ready FastAPI projects with async patterns, dependency injection, and comprehensive error handling. Use when building new FastAPI applications or setting up backend API projects.4---56# FastAPI Project Templates78Production-ready FastAPI project structures with async patterns, dependency injection, middleware, and best practices for building high-performance APIs.910## When to Use This Skill1112- Starting new FastAPI projects from scratch13- Implementing async REST APIs with Python14- Building high-performance web services and microservices15- Creating async applications with PostgreSQL, MongoDB16- Setting up API projects with proper structure and testing1718## Core Concepts1920### 1. Project Structure2122**Recommended Layout:**2324```25app/26├── api/ # API routes27│ ├── v1/28│ │ ├── endpoints/29│ │ │ ├── users.py30│ │ │ ├── auth.py31│ │ │ └── items.py32│ │ └── router.py33│ └── dependencies.py # Shared dependencies34├── core/ # Core configuration35│ ├── config.py36│ ├── security.py37│ └── database.py38├── models/ # Database models39│ ├── user.py40│ └── item.py41├── schemas/ # Pydantic schemas42│ ├── user.py43│ └── item.py44├── services/ # Business logic45│ ├── user_service.py46│ └── auth_service.py47├── repositories/ # Data access48│ ├── user_repository.py49│ └── item_repository.py50└── main.py # Application entry51```5253### 2. Dependency Injection5455FastAPI's built-in DI system using `Depends`:5657- Database session management58- Authentication/authorization59- Shared business logic60- Configuration injection6162### 3. Async Patterns6364Proper async/await usage:6566- Async route handlers67- Async database operations68- Async background tasks69- Async middleware7071## Implementation Patterns7273### Pattern 1: Complete FastAPI Application7475```python76# main.py77from fastapi import FastAPI, Depends78from fastapi.middleware.cors import CORSMiddleware79from contextlib import asynccontextmanager8081@asynccontextmanager82async def lifespan(app: FastAPI):83"""Application lifespan events."""84# Startup85await database.connect()86yield87# Shutdown88await database.disconnect()8990app = FastAPI(91title="API Template",92version="1.0.0",93lifespan=lifespan94)9596# CORS middleware97app.add_middleware(98CORSMiddleware,99allow_origins=["*"],100allow_credentials=True,101allow_methods=["*"],102allow_headers=["*"],103)104105# Include routers106from app.api.v1.router import api_router107app.include_router(api_router, prefix="/api/v1")108109# core/config.py110from pydantic_settings import BaseSettings111from functools import lru_cache112113class Settings(BaseSettings):114"""Application settings."""115DATABASE_URL: str116SECRET_KEY: str117ACCESS_TOKEN_EXPIRE_MINUTES: int = 30118API_V1_STR: str = "/api/v1"119120class Config:121env_file = ".env"122123@lru_cache()124def get_settings() -> Settings:125return Settings()126127# core/database.py128from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession129from sqlalchemy.ext.declarative import declarative_base130from sqlalchemy.orm import sessionmaker131from app.core.config import get_settings132133settings = get_settings()134135engine = create_async_engine(136settings.DATABASE_URL,137echo=True,138future=True139)140141AsyncSessionLocal = sessionmaker(142engine,143class_=AsyncSession,144expire_on_commit=False145)146147Base = declarative_base()148149async def get_db() -> AsyncSession:150"""Dependency for database session."""151async with AsyncSessionLocal() as session:152try:153yield session154await session.commit()155except Exception:156await session.rollback()157raise158finally:159await session.close()160```161162### Pattern 2: CRUD Repository Pattern163164```python165# repositories/base_repository.py166from typing import Generic, TypeVar, Type, Optional, List167from sqlalchemy.ext.asyncio import AsyncSession168from sqlalchemy import select169from pydantic import BaseModel170171ModelType = TypeVar("ModelType")172CreateSchemaType = TypeVar("CreateSchemaType", bound=BaseModel)173UpdateSchemaType = TypeVar("UpdateSchemaType", bound=BaseModel)174175class BaseRepository(Generic[ModelType, CreateSchemaType, UpdateSchemaType]):176"""Base repository for CRUD operations."""177178def __init__(self, model: Type[ModelType]):179self.model = model180181async def get(self, db: AsyncSession, id: int) -> Optional[ModelType]:182"""Get by ID."""183result = await db.execute(184select(self.model).where(self.model.id == id)185)186return result.scalars().first()187188async def get_multi(189self,190db: AsyncSession,191skip: int = 0,192limit: int = 100193) -> List[ModelType]:194"""Get multiple records."""195result = await db.execute(196select(self.model).offset(skip).limit(limit)197)198return result.scalars().all()199200async def create(201self,202db: AsyncSession,203obj_in: CreateSchemaType204) -> ModelType:205"""Create new record."""206db_obj = self.model(**obj_in.dict())207db.add(db_obj)208await db.flush()209await db.refresh(db_obj)210return db_obj211212async def update(213self,214db: AsyncSession,215db_obj: ModelType,216obj_in: UpdateSchemaType217) -> ModelType:218"""Update record."""219update_data = obj_in.dict(exclude_unset=True)220for field, value in update_data.items():221setattr(db_obj, field, value)222await db.flush()223await db.refresh(db_obj)224return db_obj225226async def delete(self, db: AsyncSession, id: int) -> bool:227"""Delete record."""228obj = await self.get(db, id)229if obj:230await db.delete(obj)231return True232return False233234# repositories/user_repository.py235from app.repositories.base_repository import BaseRepository236from app.models.user import User237from app.schemas.user import UserCreate, UserUpdate238239class UserRepository(BaseRepository[User, UserCreate, UserUpdate]):240"""User-specific repository."""241242async def get_by_email(self, db: AsyncSession, email: str) -> Optional[User]:243"""Get user by email."""244result = await db.execute(245select(User).where(User.email == email)246)247return result.scalars().first()248249async def is_active(self, db: AsyncSession, user_id: int) -> bool:250"""Check if user is active."""251user = await self.get(db, user_id)252return user.is_active if user else False253254user_repository = UserRepository(User)255```256257### Pattern 3: Service Layer258259```python260# services/user_service.py261from typing import Optional262from sqlalchemy.ext.asyncio import AsyncSession263from app.repositories.user_repository import user_repository264from app.schemas.user import UserCreate, UserUpdate, User265from app.core.security import get_password_hash, verify_password266267class UserService:268"""Business logic for users."""269270def __init__(self):271self.repository = user_repository272273async def create_user(274self,275db: AsyncSession,276user_in: UserCreate277) -> User:278"""Create new user with hashed password."""279# Check if email exists280existing = await self.repository.get_by_email(db, user_in.email)281if existing:282raise ValueError("Email already registered")283284# Hash password285user_in_dict = user_in.dict()286user_in_dict["hashed_password"] = get_password_hash(user_in_dict.pop("password"))287288# Create user289user = await self.repository.create(db, UserCreate(**user_in_dict))290return user291292async def authenticate(293self,294db: AsyncSession,295email: str,296password: str297) -> Optional[User]:298"""Authenticate user."""299user = await self.repository.get_by_email(db, email)300if not user:301return None302if not verify_password(password, user.hashed_password):303return None304return user305306async def update_user(307self,308db: AsyncSession,309user_id: int,310user_in: UserUpdate311) -> Optional[User]:312"""Update user."""313user = await self.repository.get(db, user_id)314if not user:315return None316317if user_in.password:318user_in_dict = user_in.dict(exclude_unset=True)319user_in_dict["hashed_password"] = get_password_hash(320user_in_dict.pop("password")321)322user_in = UserUpdate(**user_in_dict)323324return await self.repository.update(db, user, user_in)325326user_service = UserService()327```328329### Pattern 4: API Endpoints with Dependencies330331```python332# api/v1/endpoints/users.py333from fastapi import APIRouter, Depends, HTTPException, status334from sqlalchemy.ext.asyncio import AsyncSession335from typing import List336337from app.core.database import get_db338from app.schemas.user import User, UserCreate, UserUpdate339from app.services.user_service import user_service340from app.api.dependencies import get_current_user341342router = APIRouter()343344@router.post("/", response_model=User, status_code=status.HTTP_201_CREATED)345async def create_user(346user_in: UserCreate,347db: AsyncSession = Depends(get_db)348):349"""Create new user."""350try:351user = await user_service.create_user(db, user_in)352return user353except ValueError as e:354raise HTTPException(status_code=400, detail=str(e))355356@router.get("/me", response_model=User)357async def read_current_user(358current_user: User = Depends(get_current_user)359):360"""Get current user."""361return current_user362363@router.get("/{user_id}", response_model=User)364async def read_user(365user_id: int,366db: AsyncSession = Depends(get_db),367current_user: User = Depends(get_current_user)368):369"""Get user by ID."""370user = await user_service.repository.get(db, user_id)371if not user:372raise HTTPException(status_code=404, detail="User not found")373return user374375@router.patch("/{user_id}", response_model=User)376async def update_user(377user_id: int,378user_in: UserUpdate,379db: AsyncSession = Depends(get_db),380current_user: User = Depends(get_current_user)381):382"""Update user."""383if current_user.id != user_id:384raise HTTPException(status_code=403, detail="Not authorized")385386user = await user_service.update_user(db, user_id, user_in)387if not user:388raise HTTPException(status_code=404, detail="User not found")389return user390391@router.delete("/{user_id}", status_code=status.HTTP_204_NO_CONTENT)392async def delete_user(393user_id: int,394db: AsyncSession = Depends(get_db),395current_user: User = Depends(get_current_user)396):397"""Delete user."""398if current_user.id != user_id:399raise HTTPException(status_code=403, detail="Not authorized")400401deleted = await user_service.repository.delete(db, user_id)402if not deleted:403raise HTTPException(status_code=404, detail="User not found")404```405406### Pattern 5: Authentication & Authorization407408```python409# core/security.py410from datetime import datetime, timedelta411from typing import Optional412from jose import JWTError, jwt413from passlib.context import CryptContext414from app.core.config import get_settings415416settings = get_settings()417pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")418419ALGORITHM = "HS256"420421def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):422"""Create JWT access token."""423to_encode = data.copy()424if expires_delta:425expire = datetime.utcnow() + expires_delta426else:427expire = datetime.utcnow() + timedelta(minutes=15)428to_encode.update({"exp": expire})429encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=ALGORITHM)430return encoded_jwt431432def verify_password(plain_password: str, hashed_password: str) -> bool:433"""Verify password against hash."""434return pwd_context.verify(plain_password, hashed_password)435436def get_password_hash(password: str) -> str:437"""Hash password."""438return pwd_context.hash(password)439440# api/dependencies.py441from fastapi import Depends, HTTPException, status442from fastapi.security import OAuth2PasswordBearer443from jose import JWTError, jwt444from sqlalchemy.ext.asyncio import AsyncSession445446from app.core.database import get_db447from app.core.security import ALGORITHM448from app.core.config import get_settings449from app.repositories.user_repository import user_repository450451oauth2_scheme = OAuth2PasswordBearer(tokenUrl=f"{settings.API_V1_STR}/auth/login")452453async def get_current_user(454db: AsyncSession = Depends(get_db),455token: str = Depends(oauth2_scheme)456):457"""Get current authenticated user."""458credentials_exception = HTTPException(459status_code=status.HTTP_401_UNAUTHORIZED,460detail="Could not validate credentials",461headers={"WWW-Authenticate": "Bearer"},462)463464try:465payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[ALGORITHM])466user_id: int = payload.get("sub")467if user_id is None:468raise credentials_exception469except JWTError:470raise credentials_exception471472user = await user_repository.get(db, user_id)473if user is None:474raise credentials_exception475476return user477```478479## Testing480481```python482# tests/conftest.py483import pytest484import asyncio485from httpx import AsyncClient486from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession487from sqlalchemy.orm import sessionmaker488489from app.main import app490from app.core.database import get_db, Base491492TEST_DATABASE_URL = "sqlite+aiosqlite:///:memory:"493494@pytest.fixture(scope="session")495def event_loop():496loop = asyncio.get_event_loop_policy().new_event_loop()497yield loop498loop.close()499500@pytest.fixture501async def db_session():502engine = create_async_engine(TEST_DATABASE_URL, echo=True)503async with engine.begin() as conn:504await conn.run_sync(Base.metadata.create_all)505506AsyncSessionLocal = sessionmaker(507engine, class_=AsyncSession, expire_on_commit=False508)509510async with AsyncSessionLocal() as session:511yield session512513@pytest.fixture514async def client(db_session):515async def override_get_db():516yield db_session517518app.dependency_overrides[get_db] = override_get_db519520async with AsyncClient(app=app, base_url="http://test") as client:521yield client522523# tests/test_users.py524import pytest525526@pytest.mark.asyncio527async def test_create_user(client):528response = await client.post(529"/api/v1/users/",530json={531"email": "[email protected]",532"password": "testpass123",533"name": "Test User"534}535)536assert response.status_code == 201537data = response.json()538assert data["email"] == "[email protected]"539assert "id" in data540```541