💻 Développement

dev-fastapi-guide

Développement d'APIs performantes avec FastAPI, Pydantic, async/await, OpenAPI et système de dépendances.

⚡ Installation & lancement en 1 commande

Copiez-collez dans votre terminal : le skill s'installe dans ~/.claude/skills et Claude Code se lance directement dessus.

macOS / Linux
curl -fsSL https://raw.githubusercontent.com/khalilbenaz/claude-skills-collection/main/install.sh | sh -s -- dev-fastapi-guide --launch
Windows (PowerShell)
iex "& { $(iwr -useb https://raw.githubusercontent.com/khalilbenaz/claude-skills-collection/main/install.ps1) } dev-fastapi-guide -Launch"

🚀 Déjà installé ?

claude "/dev-fastapi-guide"

Ou tapez /dev-fastapi-guide dans une session Claude Code, ou décrivez simplement votre besoin — le skill se déclenche automatiquement via le skill-router.

🔑 Déclencheurs automatiques

Le skill s'active automatiquement quand votre demande contient :

FastAPIPydanticAPI Pythonasync Python APIuvicorn

📦 Installation manuelle

git clone https://github.com/khalilbenaz/claude-skills-collection.git cp -r claude-skills-collection/skills/dev-fastapi-guide ~/.claude/skills/

Payload du plugin : skills/dev-fastapi-guide · source éditable : dev-skills/fastapi-guide

📖 Manuel

Guide FastAPI

Workflow

1. Bootstrapper le projet

pip install "fastapi[standard]" sqlalchemy[asyncio] alembic pydantic-settings httpx pytest pytest-asyncio

Structure recommandée :

app/
  main.py          # lifespan, include_router, middleware
  config.py        # Settings via pydantic-settings
  dependencies.py  # Depends() partagés (db, auth, pagination)
  routers/
    users.py
    items.py
  schemas/
    user.py        # UserCreate, UserUpdate, UserResponse
  models/
    user.py        # SQLAlchemy ORM
  services/
    user_service.py
  repositories/
    user_repo.py
tests/
  conftest.py      # fixtures DB, client async
  test_users.py

2. Config typée avec pydantic-settings

# app/config.py
from pydantic_settings import BaseSettings

class Settings(BaseSettings):
    database_url: str
    secret_key: str
    debug: bool = False
    cors_origins: list[str] = []

    model_config = {"env_file": ".env"}

settings = Settings()

3. Schémas Pydantic v2 — séparer Create / Update / Response

# app/schemas/user.py
from pydantic import BaseModel, EmailStr, Field
from datetime import datetime

class UserCreate(BaseModel):
    email: EmailStr
    password: str = Field(min_length=8)
    username: str = Field(max_length=50)

class UserUpdate(BaseModel):
    username: str | None = Field(None, max_length=50)

class UserResponse(BaseModel):
    id: int
    email: EmailStr
    username: str
    created_at: datetime

    model_config = {"from_attributes": True}

Règle : jamais réutiliser UserCreate comme response_model — le schéma de réponse ne doit pas contenir le mot de passe.

4. Lifespan + application factory

# app/main.py
from contextlib import asynccontextmanager
from fastapi import FastAPI
from app.routers import users, items

@asynccontextmanager
async def lifespan(app: FastAPI):
    # startup
    yield
    # shutdown

def create_app() -> FastAPI:
    app = FastAPI(
        title="Mon API",
        lifespan=lifespan,
        docs_url="/docs" if settings.debug else None,
    )
    app.include_router(users.router, prefix="/users", tags=["users"])
    app.include_router(items.router, prefix="/items", tags=["items"])
    return app

app = create_app()

5. Dépendances réutilisables

# app/dependencies.py
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from sqlalchemy.ext.asyncio import AsyncSession
from app.db import get_session
from app.services.auth import decode_token

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/token")

async def get_current_user(
    token: str = Depends(oauth2_scheme),
    db: AsyncSession = Depends(get_session),
):
    payload = decode_token(token)
    if not payload:
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
    return await user_repo.get_by_id(db, payload["sub"])

# Pagination simple
def pagination(skip: int = 0, limit: int = Query(20, le=100)):
    return {"skip": skip, "limit": limit}

6. Router et endpoints

# app/routers/users.py
from fastapi import APIRouter, Depends, status
from app.schemas.user import UserCreate, UserResponse
from app.services.user_service import UserService
from app.dependencies import get_current_user, pagination

router = APIRouter()

@router.get("/", response_model=list[UserResponse])
async def list_users(
    page: dict = Depends(pagination),
    service: UserService = Depends(),
):
    return await service.list(**page)

@router.post("/", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
async def create_user(payload: UserCreate, service: UserService = Depends()):
    return await service.create(payload)

@router.get("/me", response_model=UserResponse)
async def get_me(current_user=Depends(get_current_user)):
    return current_user

7. Async — quand l'utiliser

CasDécorateur
Requête BDD async (asyncpg, asyncmy)async def
Appel HTTP externe (httpx)async def
Calcul CPU pur, bibliothèque syncdef (thread pool auto)
Lecture fichier blockingdef ou run_in_executor

Règle critique : ne jamais appeler une librairie synchrone (ex: requests, psycopg2 standard) dans un async def — ça bloque le event loop pour tous les workers.

8. SQLAlchemy async

# app/db.py
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker

engine = create_async_engine(settings.database_url, pool_size=10, max_overflow=20)
AsyncSessionLocal = async_sessionmaker(engine, expire_on_commit=False)

async def get_session():
    async with AsyncSessionLocal() as session:
        yield session

9. Gestion des erreurs

from fastapi import Request
from fastapi.responses import JSONResponse

class AppError(Exception):
    def __init__(self, code: str, status: int = 400):
        self.code = code
        self.status = status

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

Utiliser HTTPException pour les erreurs HTTP standard, AppError pour les erreurs métier avec codes lisibles.

10. Tests avec httpx + pytest-asyncio

# tests/conftest.py
import pytest_asyncio
from httpx import AsyncClient, ASGITransport
from app.main import app

@pytest_asyncio.fixture
async def client():
    async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac:
        yield ac
# tests/test_users.py
import pytest

@pytest.mark.asyncio
async def test_create_user(client):
    r = await client.post("/users/", json={"email": "a@b.com", "password": "secret123", "username": "alice"})
    assert r.status_code == 201
    assert r.json()["email"] == "a@b.com"

11. Déploiement

# Développement
fastapi dev app/main.py

# Production (Gunicorn + Uvicorn workers)
gunicorn app.main:app -w 4 -k uvicorn.workers.UvicornWorker --bind 0.0.0.0:8000
# Dockerfile multi-stage
FROM python:3.12-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

FROM python:3.12-slim
WORKDIR /app
COPY --from=builder /usr/local/lib/python3.12 /usr/local/lib/python3.12
COPY . .
CMD ["gunicorn", "app.main:app", "-w", "4", "-k", "uvicorn.workers.UvicornWorker", "--bind", "0.0.0.0:8000"]

Garde-fous / Anti-patterns

Bonnes pratiques 2026