Validation et codes de retour À la page précédente, FastAPI refusait déjà un prix en lettres ou un nom manquant, mais avec son propre format d'erreur. Et il acceptait un nom vide ou un prix négatif. Corrigeons tout ça. Règle n°1 d'une API : ne jamais faire confiance au client . Tout ce qui arrive (body, paramètres, headers) doit être validé. Un format d'erreur unique Toutes les erreurs seront renvoyées au format application/problem+json (chapitre 5). On commence par une fonction utilitaire : from fastapi.responses import JSONResponse def problem(status, title, detail, **extra): return JSONResponse( {"type": "about:blank", "title": title, "status": status, "detail": detail, **extra}, status_code=status, media_type="application/problem+json", ) Valider le body On décrit la forme attendue dans un schéma Pydantic : longueurs, valeurs minimales, champs inconnus refusés. from pydantic import BaseModel, ConfigDict, Field class CocktailIn(BaseModel): model_config = ConfigDict(extra="forbid") # refuse les champs inconnus name: str = Field(min_length=1, max_length=50) price: float = Field(gt=0) alcohol_free: bool = False @app.post("/v1/cocktails", status_code=201) def create_cocktail(cocktail: CocktailIn, response: Response): global next_id if any(c["name"] == cocktail.name for c in cocktails): return problem(409, "Conflict", "Le cocktail " + cocktail.name + " existe déjà.") new = {"id": next_id, **cocktail.model_dump()} next_id += 1 cocktails.append(new) response.headers["Location"] = "/v1/cocktails/" + str(new["id"]) return new Quand la fonction s'exécute, cocktail a déjà été validé : il ne contient que les champs déclarés, avec des valeurs correctes. On peut l'utiliser sans risque. Les erreurs de FastAPI au bon format FastAPI valide tout seul, mais renvoie ses erreurs dans son propre format ( {"detail": [...]} ). On le remplace par des gestionnaires d'exceptions : from http import HTTPStatus from fastapi import Request from fastapi.exceptions import RequestValidationError from starlette.exceptions import HTTPException as StarletteHTTPException @app.exception_handler(RequestValidationError) async def on_validation_error(request: Request, exc: RequestValidationError): errors = [ {"field": ".".join(str(p) for p in e["loc"][1:]), "message": e["msg"]} for e in exc.errors() ] return problem(422, "Données invalides", "La requête contient des erreurs.", errors=errors) @app.exception_handler(StarletteHTTPException) async def on_http_error(request: Request, exc: StarletteHTTPException): return problem(exc.status_code, HTTPStatus(exc.status_code).phrase, str(exc.detail)) @app.exception_handler(Exception) async def on_crash(request: Request, exc: Exception): return problem(500, "Internal Server Error", "Le barman a fait tomber un verre. Réessayez plus tard.") Désormais, raise HTTPException(status_code=404, detail="Cocktail introuvable") produit une réponse problem+json . En production, une erreur 500 ne doit jamais renvoyer la stack trace au client. On la logge côté serveur, et on renvoie un message neutre. Quel code renvoyer ? Situation Code Le body n'est pas du JSON 400 (FastAPI renvoie 422 , c'est acceptable) Pas de clé, ou clé invalide 401 Clé valide, mais action interdite 403 Ressource inexistante (y compris dans le chemin : /cocktails/999/reviews ) 404 Conflit avec l'état actuel (doublon, état qui ne permet pas l'action) 409 JSON valide, mais données incorrectes 422 Création réussie 201 + Location Suppression réussie 204 Dans quel ordre tester ? D'abord l'authentification ( 401 ), puis l'existence de la ressource ( 404 ), puis la validité des données ( 422 ), puis les règles métier ( 409 ). FastAPI valide le body avant d'exécuter votre fonction : il renverra un 422 avant que vous ayez pu vérifier la clé ou l'existence du cocktail. Pour respecter l'ordre ci-dessus, vérifiez l'authentification avec une dépendance ( Depends ), qui s'exécute avant la validation du body.