Construire une API REST moderne avec FastAPI : guide technique

FastAPI 0.115, combiné à Python 3.12, change la donne pour le développement backend. Lancé en 2018, ce framework s'appuie sur les annotations de type pour offrir un développement rapide, une documentation automatique et des performances qui rivalisent avec Node.js ou Go. Si vous cherchez à construire une API REST FastAPI complète et maintenable, ce guide couvre l'essentiel : modélisation des données, endpoints CRUD, validation et mise en production.

Pourquoi FastAPI plutôt qu'un autre framework ?

Mon constat : Flask reste pertinent pour les micro-projets de moins de 50 routes. Au-delà, FastAPI l'écrase — et de loin. Là où Flask exige une configuration manuelle des validateurs et une gestion laborieuse de la sérialisation, FastAPI génère tout ça automatiquement depuis vos types Python. La documentation interactive (OpenAPI et Swagger UI) est livrée sans rien demander.

Le socle technique repose sur FastAPI lui-même, construit sur Starlette (couche ASGI) et Uvicorn comme serveur. Les benchmarks internes le placent parmi les frameworks Python les plus rapides, particulièrement sous charge concurrente. Django impose une structure rigide, Flask vous laisse vous organiser mais sans filet. FastAPI trouve le juste milieu.

Mise en place du projet

L'installation se fait en quelques commandes :

pip install fastapi uvicorn sqlalchemy pydantic

La structure de projet recommandée suit une logique par domaine fonctionnel :

mon_api/
├── app/
│   ├── __init__.py
│   ├── main.py
│   ├── models/
│   ├── schemas/
│   ├── routers/
│   └── database.py
├── requirements.txt
└── .env

Cette architecture permet de maintenir une séparation claire entre la couche de données (models), la validation (schemas) et le routage des endpoints (routers).

Modélisation avec SQLAlchemy et Pydantic

La double modélisation constitue l'une des forces de FastAPI. SQLAlchemy gère le mapping objet-relationnel et les migrations, tandis que Pydantic assure la validation et la sérialisation des données échangées avec le client.

# database.py
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, declarative_base

DATABASE_URL = "postgresql://user:pass@localhost/db"
engine = create_engine(DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
# models/item.py
from sqlalchemy import Column, Integer, String, Float
from app.database import Base

class Item(Base):
    __tablename__ = "items"

    id = Column(Integer, primary_key=True, index=True)
    name = Column(String, index=True)
    description = Column(String)
    price = Column(Float)
# schemas/item.py
from pydantic import BaseModel

class ItemCreate(BaseModel):
    name: str
    description: str | None = None
    price: float

class ItemResponse(ItemCreate):
    id: int

    class Config:
        from_attributes = True

Création des endpoints REST

Le routeur expose les opérations CRUD classiques. L'injection de dépendances de FastAPI permet de gérer proprement les sessions de base de données :

# routers/items.py
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from app import schemas, models
from app.database import SessionLocal

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

def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

@router.post("/", response_model=schemas.ItemResponse)
def create_item(item: schemas.ItemCreate, db: Session = Depends(get_db)):
    db_item = models.Item(**item.model_dump())
    db.add(db_item)
    db.commit()
    db.refresh(db_item)
    return db_item

@router.get("/{item_id}", response_model=schemas.ItemResponse)
def read_item(item_id: int, db: Session = Depends(get_db)):
    db_item = db.query(models.Item).filter(models.Item.id == item_id).first()
    if not db_item:
        raise HTTPException(status_code=404, detail="Item not found")
    return db_item

@router.get("/", response_model=list[schemas.ItemResponse])
def list_items(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
    return db.query(models.Item).offset(skip).limit(limit).all()

Ce pattern — un endpoint POST pour la création, un GET pour la lecture individuelle, un GET paginé pour la liste — constitue la base de toute API REST bien conçue.

Gestion des erreurs et validation

FastAPI retourne automatiquement des messages d'erreur structurés lorsque les données envoyées ne respectent pas les schémas Pydantic. On peut également définir des validateurs personnalisés :

from pydantic import validator

class ItemCreate(BaseModel):
    name: str
    price: float

    @validator("price")
    def price_must_be_positive(cls, v):
        if v <= 0:
            raise ValueError("Le prix doit être supérieur à zéro")
        return v

La couche de validation étant centralisée dans les schémas, le code des routeurs reste lisible et se concentre sur la logique métier.

Déploiement et mise en production

En production, le serveur Uvicorn doit être piloté par un superviseur de processus. Gunicorn avec Uvicorn workers constitue la solution recommandée :

gunicorn app.main:app -w 4 -k uvicorn.workers.UvicornWorker --bind 0.0.0.0:8000

L'ajout de CORS, du rate limiting et d'une solution de logging structuré (structlog ou loguru) est vivement conseillé avant exposition publique. Pour déployer votre API FastAPI en production, notre guide Docker explique la mise en place d'un environnement conteneurisé complet. La documentation Swagger générée automatiquement est accessible à l'adresse /docs une fois l'application lancée.

Au final, le choix de FastAPI se justifie par trois critères : la performance, la documentation automatique et la maintenabilité. Si votre projet dépasse les 50 routes ou nécessite une API publique bien documentée, la question ne se pose même pas.