FastAPI en production : ce qui compte vraiment
Cinq réglages qu'on ne voit dans aucun tutoriel et qui font la différence entre une API qui tourne et une API qu'on peut exploiter.
FastAPI rend la première version facile. Trop facile, peut-être : on a une API documentée en une heure, et on croit avoir fini. Ce qui suit, c'est ce que j'ajoute systématiquement avant qu'un service ne touche de vrais utilisateurs. Rien de spectaculaire. Tout est là parce qu'un incident l'a mis là.
1. Une seule forme d'erreur
Une API qui renvoie tantôt {"detail": "..."}, tantôt une trace Python, tantôt du HTML, c'est une API que le frontend ne peut pas traiter. Toute erreur, sans exception, doit sortir dans la même forme.
class AppError(Exception):
status_code = 400
code = "app_error"
def __init__(self, message: str) -> None:
self.message = message
@app.exception_handler(AppError)
async def handle_app_error(_: Request, exc: AppError) -> JSONResponse:
return JSONResponse(
status_code=exc.status_code,
content={"detail": exc.message, "code": exc.code},
)Chaque erreur métier hérite d'AppError et porte un code stable. Le client se branche sur le code, jamais sur le message — le message change, le code non. Et les erreurs non prévues passent par un dernier gestionnaire qui journalise la trace et renvoie un 500 sobre. La trace ne sort jamais.
2. Une session par requête, un commit explicite
Le piège classique de l'async : une session SQLAlchemy partagée entre requêtes, ou ouverte et jamais fermée. La dépendance qui règle le problème tient en dix lignes et ne se discute pas.
async def get_session() -> AsyncGenerator[AsyncSession, None]:
async with SessionLocal() as session:
try:
yield session
await session.commit()
except Exception:
await session.rollback()
raiseLe commit est à la fin de la requête, le rollback sur toute exception. Un service n'appelle jamais commit() lui-même : s'il le faisait, une erreur survenue après laisserait une transaction à moitié validée.
Et pool_pre_ping=True sur le moteur. Sans lui, la première requête après une coupure réseau ou un redémarrage de la base échoue sur une connexion morte. Avec lui, elle est testée et remplacée. Ça coûte un aller-retour ; ça évite un incident par semaine.
3. Des délais partout
Tout appel sortant a un délai maximal : base de données, SMTP, API tierce. Un envoi d'e-mail qui attend un serveur muet bloque un worker ; dix envois bloquent l'API.
Et le délai est une valeur de configuration, pas une constante dans le code. En production on veut pouvoir le baisser sans redéployer.
4. Une sonde qui dit la vérité
/health qui renvoie {"status": "ok"} sans rien vérifier ne sert qu'à rassurer. La sonde doit interroger ce dont l'API dépend réellement — au minimum la base, avec un SELECT 1 — et renvoyer un 503 si ça ne répond pas.
C'est ce que l'hébergeur regarde pour décider de router le trafic. Une sonde qui ment envoie des utilisateurs vers un service mort.
5. La configuration validée au démarrage
Une variable d'environnement absente doit faire échouer le démarrage, avec un message qui dit laquelle. Pas une erreur à la première requête qui l'utilise, une heure plus tard, pour un utilisateur.
class Settings(BaseSettings):
database_url: PostgresDsn
secret_key: str = Field(min_length=32)
allowed_origins: list[str]
model_config = SettingsConfigDict(env_file=".env")Pydantic fait ce travail gratuitement. Une clé secrète de moins de 32 caractères, un DSN mal formé, une origine CORS oubliée : tout est refusé avant que le premier port ne s'ouvre.
Ce qu'il n'y a pas dans cette liste
Pas de cache, pas de compression, pas de réglage du nombre de workers. Ce sont des optimisations, et les optimisations viennent avec des mesures. Les cinq points ci-dessus ne sont pas des optimisations : ce sont les conditions pour qu'on puisse, un jour, mesurer quelque chose.
À lire ensuite
Un système de paiement commence par un ledger, pas par une API
Pourquoi la comptabilité à double entrée n'est pas un détail d'implémentation quand on fait circuler l'argent des autres.
Une deadline se négocie sur le périmètre, jamais sur la qualité
Ce qu'on sacrifie quand on accepte de livrer plus vite, et pourquoi ce n'est jamais la partie qu'on croit.
Le formulaire que personne ne remplit
Ce que six sites vitrines camerounais m'ont appris sur le parcours client réel — et pourquoi WhatsApp a remplacé le panier.