Qu'est-ce que FastAPI ?

Découvrez le fonctionnement de FastAPI.

Publié le par Gabriel Trouvé (mis à jour le )

62 minutes

Comment faire en sorte qu'une application mobile, un site web ou même le programme de votre collègue Patrick puisse interagir avec votre application ? C'est le rôle d'une API : créer une porte d'entrée vers vos fonctionnalités que n'importe quel client peut ensuite utiliser (selon vos restrictions bien sûr !).

Pendant longtemps, pour créer une API en Python, Flask et Django étaient les choix les plus évidents. Nous avons d'ailleurs un article sur la création d'une API REST avec Django REST Framework.

Mais depuis quelques années, un troisième framework a bousculé la hiérarchie : FastAPI. D'après le Stack Overflow Developer Survey 2025, la progression de FastAPI ne passe pas inaperçue.

Extrait du Stack Overflow Developer Survey 2025

Extrait du Stack Overflow Developer Survey 2025

Qu'est-ce qu'une API REST ?

Je vous invite vivement à consulter notre glossaire concernant le terme API.

Une API (Application Programming Interface) est une porte d'entrée vers votre programme. Un client (navigateur, application mobile, etc.) envoie une requête HTTP, votre API la traite et renvoie une réponse en JSON (généralement).

Exemple :

  • Le client demande une information à votre API via GET /utilisateurs/1

  • Votre API va chercher l'utilisateur numéro 1

  • Elle renvoie {"nom": "Patrick", "email": "[email protected]"}

Le client n'a pas besoin de connaître votre code, il lui suffit de manipuler le JSON reçu pour en faire ce qu'il veut.

Qu'est-ce que FastAPI ?

FastAPI est un framework open source créé par Sebastián Ramírez, sorti fin 2018 dans le but de créer des API performantes en s'appuyant sur les fonctionnalités modernes de Python comme le type hints ou l'asynchrone.

Au moment où j'écris ces lignes, le dépôt GitHub dépasse les 101 000 étoiles.

Voici trois points forts de FastAPI :

  • La rapidité : FastAPI est construit sur Starlette, un framework web asynchrone dont la classe FastAPI hérite, le tout servi par le serveur Uvicorn

  • Le typage : vous annotez vos fonctions, FastAPI s'occupe de la validation, de la conversion et de la documentation

  • La documentation automatique : chaque API génère sa propre documentation interactive, sans écrire une seule ligne supplémentaire

Je vous conseille donc de lire notre article sur le typage en Python 🐍.

Installation et première API

Créez un dossier pour votre projet fast_api_tuto, naviguez à l'intérieur de ce dernier, créez votre environnement virtuel, activez-le et installez FastAPI :

pip install "fastapi[standard]"
SHELL

À noter

L'installation standard installe FastAPI avec des dépendances recommandées, comme Uvicorn (le serveur) et FastAPI CLI que nous allons utiliser ensuite.

Créons maintenant un fichier main.py :

# main.py
from fastapi import FastAPI

# On crée l'instance de notre application
app = FastAPI()


# Le décorateur indique que cette fonction répond aux requêtes GET sur /
@app.get("/")
def accueil():
    return {"message": "Bienvenue sur mon API"}
PYTHON

Lancez votre API :

fastapi dev main.py
SHELL

Cette commande lance un serveur de développement qui recharge automatiquement l'application à chaque modification du code. Le terminal vous indique que l'API est disponible sur http://127.0.0.1:8000/.

Rendez-vous sur cette adresse pour voir le résultat :

{
  "message": "Bienvenue sur mon API"
}
JSON

Il s'agit bien du JSON renvoyé par votre fonction : FastAPI a automatiquement converti le dictionnaire en JSON.

Et ce n'est pas tout : vous avez sûrement remarqué l'adresse http://127.0.0.1:8000/docs. FastAPI a généré une documentation interactive complète de votre API via Swagger UI. Chaque endpoint y est listé, et vous pouvez même les tester directement depuis le navigateur avec le bouton Try it out.

Documentation Swagger UI

Documentation Swagger UI

Une seconde interface est disponible sur http://127.0.0.1:8000/redoc, une alternative à Swagger UI : ReDoc.

À noter

Cette documentation repose sur le standard OpenAPI. Swagger UI et ReDoc sont alimentés par le schéma disponible à cette adresse : http://127.0.0.1:8000/openapi.json.

Le typage

Si vous avez l'habitude d'entendre que le typage reste indicatif à l'exécution, FastAPI change la donne : le framework utilise vos annotations pour valider et convertir les données à l'exécution.

Les paramètres de chemin

Ajoutons un endpoint qui récupère un utilisateur par son identifiant :

# On déclare user_id dans le chemin ET on le type dans la fonction
@app.get("/utilisateurs/{user_id}")
def obtenir_utilisateur(user_id: int):
    return {"user_id": user_id, "nom": "Patrick"}
PYTHON

Bon... ok, rien n'est dynamique. Peu importe l'entier, l'API renvoie toujours Patrick, mais c'est pour comprendre le principe.

Grâce à l'annotation user_id: int :

  • Une requête sur http://127.0.0.1:8000/utilisateurs/1 fonctionne et user_id vaut bien l'entier 1
{
  "user_id": 1,
  "nom": "Patrick"
}
JSON
  • Une requête sur http://127.0.0.1:8000/utilisateurs/patrick est refusée automatiquement
{
  "detail": [
    {
      "type": "int_parsing",
      "loc": [
        "path",
        "user_id"
      ],
      "msg": "Input should be a valid integer, unable to parse string as an integer",
      "input": "patrick"
    }
  ]
}
JSON

L'annotation de type suffit pour la validation 😎.

Les paramètres de requête

Les paramètres déclarés dans la fonction mais absents du chemin deviennent automatiquement des paramètres de requête (ce qui se trouve après le ? dans l'URL).

articles = [
    {"titre": "Qu'est-ce que uv ?", "publie": True},
    {"titre": "Qu'est-ce que Ruff ?", "publie": True},
    {"titre": "Le typage en Python", "publie": True},
    {"titre": "C'est quoi le RAG ?", "publie": False},
]


# limite et publie ne sont pas dans le chemin : ce sont des paramètres de requête
@app.get("/articles")
def lister_articles(limite: int = 10, publie: bool = True):
    resultats = [a for a in articles if a["publie"] == publie]
    return resultats[:limite]
PYTHON

Une requête sur http://127.0.0.1:8000/articles renvoie les trois articles publiés, car il s'agit des valeurs par défaut : limite à 10 et publie à True.

Ajoutez ?limite=2 et vous n'obtiendrez plus que les deux premiers. Ajoutez ?publie=false et c'est l'article non publié qui apparaît (http://127.0.0.1:8000/articles?limite=2&publie=false) :

[
  {
    "titre": "C'est quoi le RAG ?",
    "publie": false
  }
]
JSON

Les paramètres de requête permettent ainsi de filtrer, de trier ou de paginer une ressource.

Validations plus fines

Si on veut aller plus loin, comme pour imposer qu'un identifiant soit positif ou qu'un terme de recherche fasse au moins un certain nombre de caractères, FastAPI fournit Path et Query.

Path et Query sont des fonctions qui ajoutent des règles de validation par-dessus le type. Path s'utilise sur un paramètre de chemin et Query sur un paramètre de requête.

# main.py
from typing import Annotated
from fastapi import FastAPI, Path, Query


# gt=0 : l'identifiant doit être strictement supérieur à 0
@app.get("/utilisateurs/{user_id}")
def obtenir_utilisateur(user_id: Annotated[int, Path(gt=0)]):
    return {"user_id": user_id, "nom": "Patrick"}


# min_length et max_length contraignent la longueur du terme de recherche
@app.get("/recherche")
def rechercher(q: Annotated[str, Query(min_length=3, max_length=50)]):
    return {"recherche": q}
PYTHON

Une requête sur http://127.0.0.1:8000/utilisateurs/0 est rejetée, car 0 n'est pas strictement supérieur à 0 :

{
  "detail": [
    {
      "type": "greater_than",
      "loc": [
        "path",
        "user_id"
      ],
      "msg": "Input should be greater than 0",
      "input": "0",
      "ctx": {
        "gt": 0
      }
    }
  ]
}
JSON

Une recherche trop courte comme http://127.0.0.1:8000/recherche?q=ab renvoie une erreur, car elle fait moins de 3 caractères :

{
  "detail": [
    {
      "type": "string_too_short",
      "loc": [
        "query",
        "q"
      ],
      "msg": "String should have at least 3 characters",
      "input": "ab",
      "ctx": {
        "min_length": 3
      }
    }
  ]
}
JSON

Voici quelques exemples de contraintes :

  • gt, ge, lt, le : supérieur, supérieur ou égal, inférieur, inférieur ou égal (pour les nombres)

  • min_length, max_length : longueur d'une chaîne

  • pattern : validation par expression régulière

Ces règles apparaissent automatiquement dans votre documentation.

Pydantic : le cœur de la validation

Très souvent, vous aurez à gérer des données complexes comme le corps d'une requête POST contenant du JSON. Pydantic entre en jeu : c'est la bibliothèque de validation de données sur laquelle FastAPI repose entièrement.

Avec Pydantic, les type hints ne documentent plus seulement le code : ils le font respecter.

D'ailleurs, la documentation de FastAPI commence par les types. Un modèle Pydantic, c'est une classe qui hérite de BaseModel où chaque attribut est décrit par un type Python standard.

from pydantic import BaseModel


class Article(BaseModel):
    titre: str
    vues: int
    publie: bool
    note: float
PYTHON

La bibliothèque fournit des types spéciaux qui encapsulent des règles de validation courantes. Par exemple, au lieu de typer un attribut email avec str, on utilisera EmailStr.

La fonction Field permet d'ajouter des contraintes sur chaque attribut, exactement comme Path et Query le faisaient pour les paramètres.

from pydantic import BaseModel, EmailStr, Field


class Article(BaseModel):
    # Le titre doit faire entre 1 et 100 caractères
    titre: str = Field(min_length=1, max_length=100)
    # Le nombre de vues ne peut pas être négatif, et vaut 0 par défaut
    vues: int = Field(default=0, ge=0)
    # Un vrai email, validé par Pydantic
    auteur_email: EmailStr
PYTHON

Testons ce modèle avec des données :

from pydantic import BaseModel, EmailStr, Field, ValidationError


class Article(BaseModel):
    # Le titre doit faire entre 1 et 100 caractères
    titre: str = Field(min_length=1, max_length=100)
    # Le nombre de vues ne peut pas être négatif, et vaut 0 par défaut
    vues: int = Field(default=0, ge=0)
    # Un vrai email, validé par Pydantic
    auteur_email: EmailStr

try:
    Article(titre="Test", vues=-5, auteur_email="pas-un-email")
except ValidationError as e:
    for erreur in e.errors():
        print(erreur["loc"], "->", erreur["msg"])
PYTHON

Si vous exécutez le script, Pydantic remonte précisément deux problèmes :

('vues',) -> Input should be greater than or equal to 0
('auteur_email',) -> value is not a valid email address: An email address must have an @-sign.
SHELL

À noter

Ce dernier exemple n'importe pas FastAPI : Pydantic est une bibliothèque autonome.

C'est exactement ce mécanisme que FastAPI utilise en interne pour valider les requêtes.

Puisque FastAPI s'appuie sur Pydantic, il suffit de typer un paramètre de la fonction avec notre modèle pour recevoir et valider les données :

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class UtilisateurEntree(BaseModel):
    nom: str
    email: str
    mot_de_passe: str
    # Champ optionnel grâce à la valeur par défaut
    bio: str | None = None


@app.post("/utilisateurs")
def creer_utilisateur(utilisateur: UtilisateurEntree):
    # On simule un enregistrement en base de données
    return utilisateur
PYTHON

FastAPI comprend que utilisateur provient du corps de la requête. Il lit le JSON, le valide avec Pydantic et fournit un objet Python doté de utilisateur.nom et utilisateur.email.

Et si le client envoie des données invalides, par exemple en n'envoyant que le nom via Swagger UI :

Utilisation de Swagger UI

Utilisation de Swagger UI

FastAPI indique les champs manquants :

{
  "detail": [
    {
      "type": "missing",
      "loc": [
        "body",
        "email"
      ],
      "msg": "Field required",
      "input": {
        "nom": "patrick"
      }
    },
    {
      "type": "missing",
      "loc": [
        "body",
        "mot_de_passe"
      ],
      "msg": "Field required",
      "input": {
        "nom": "patrick"
      }
    }
  ]
}
JSON

À noter

La structure de l'erreur contient une entrée par champ manquant : la première ("loc": ["body", "email"]) signale que l'email est absent, la seconde que mot_de_passe l'est aussi. Le champ input de chaque entrée ne montre pas la valeur du champ manquant (il n'y en a pas, justement), mais l'intégralité du corps de la requête reçue par FastAPI ({"nom": "Patrick"}).

Un problème se pose avec notre endpoint : il renvoie l'utilisateur complet, mot de passe inclus 👾. D'ailleurs, nous sommes d'accord pour dire que nous n'avons même pas haché le mot de passe, mais ce n'est pas le sujet ici.

FastAPI propose le paramètre response_model : on définit un second modèle décrivant ce que l'API a le droit de renvoyer.

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class UtilisateurEntree(BaseModel):
    nom: str
    email: str
    mot_de_passe: str
    # Champ optionnel grâce à la valeur par défaut
    bio: str | None = None


class UtilisateurSortie(BaseModel):
    nom: str
    email: str
    bio: str | None = None


@app.post("/utilisateurs", response_model=UtilisateurSortie)
def creer_utilisateur(utilisateur: UtilisateurEntree):
    # On simule un enregistrement en base de données
    return utilisateur
PYTHON

De cette manière, FastAPI filtre la réponse à travers UtilisateurSortie. Évidemment, la documentation continue de se mettre à jour toute seule 😎.

Documentation pour le corps de la requête et la réponse

Documentation pour le corps de la requête et la réponse

Gérer les erreurs

Nos endpoints envoient des données en supposant que tout se passe bien. Accéder à une clé absente d'un dictionnaire lève une KeyError, et le client reçoit une erreur 500 Internal Server Error brute sans JSON, et de plus non documentée.

Lever une erreur avec HTTPException

FastAPI fournit HTTPException pour envoyer une erreur explicite. Reprenons notre recherche avec un dictionnaire en guise de base de données :

from fastapi import FastAPI, HTTPException, status

app = FastAPI()

# Fausse "base de données" pour l'exemple
utilisateurs_db = {
    1: {"nom": "Patrick", "email": "[email protected]"},
    2: {"nom": "Sebastien", "email": "[email protected]"},
}


@app.get("/utilisateurs/{user_id}")
def obtenir_utilisateur(user_id: int):
    if user_id not in utilisateurs_db:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Utilisateur introuvable")
    return utilisateurs_db[user_id]
PYTHON

Une requête sur /utilisateurs/3 renvoie maintenant un code 404 propre :

{
    "detail": "Utilisateur introuvable"
}
JSON

À noter

Pour ma part, je continue de tester directement dans Swagger UI via "Try it out".

Documenter les erreurs dans Swagger UI

Si vous testez cet endpoint depuis la documentation, vous remarquerez que le 404 s'affiche comme Undocumented. Ce n'est pas un bug : par défaut, FastAPI ne déclare dans le schéma OpenAPI que les réponses de succès et de validation.

Pour documenter l'erreur 404, on utilise le paramètre responses du décorateur :

@app.get(
    "/utilisateurs/{user_id}",
    responses={status.HTTP_404_NOT_FOUND: {"description": "Utilisateur non trouvé"}},
)
def obtenir_utilisateur(user_id: int):
    if user_id not in utilisateurs_db:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Utilisateur introuvable")
    return utilisateurs_db[user_id]
PYTHON

Le 404 apparaît désormais dans la documentation :

Documentation complète

Documentation complète

Choisir le bon code de statut

Par défaut, FastAPI renvoie un code 200 en cas de succès. Mais quand on crée une ressource, la convention REST recommande un statut 201 "Created". On le précise alors dans le décorateur :

from fastapi import FastAPI, status
from pydantic import BaseModel

app = FastAPI()


class UtilisateurEntree(BaseModel):
    nom: str
    email: str
    mot_de_passe: str


class UtilisateurSortie(BaseModel):
    nom: str
    email: str


# status.HTTP_201_CREATED est plus lisible que d'écrire 201 en dur
@app.post(
    "/utilisateurs",
    response_model=UtilisateurSortie,
    status_code=status.HTTP_201_CREATED,
)
def creer_utilisateur(utilisateur: UtilisateurEntree):
    return utilisateur
PYTHON

Une requête POST réussie renvoie désormais un code 201. À la différence de l'erreur 404 vue plus haut, ce code 201 est documenté automatiquement, car status_code déclare la réponse de succès officielle de l'endpoint. Le paramètre responses sert uniquement à documenter des réponses additionnelles.

Panorama de l'écosystème

FastAPI reste volontairement minimaliste : validation, routage, documentation. Pour le reste, comme la base de données, l'authentification, la configuration et les tests, le framework s'appuie sur un écosystème de bibliothèques tierces. Voici les briques que vous croiserez certainement sur votre route.

SQLModel

Écrit par le créateur de FastAPI lui-même, SQLModel combine Pydantic et SQLAlchemy (la référence des ORM en Python). Un même modèle sert à la fois de table en base de données et de schéma de validation. Il est souvent complété par Alembic, l'outil de migrations de SQLAlchemy. Vous vous doutez bien que le sujet mérite un article à lui tout seul 😉.

fastapi-users

Pour l'authentification : inscription, connexion, vérification d'email, OAuth2, la bibliothèque fastapi-users fournit des routes prêtes à l'emploi.

À noter

Au moment où j'écris ces lignes, fastapi-users est en mode maintenance : plus de nouvelles fonctionnalités, uniquement des correctifs de sécurité. L'équipe est en train de travailler sur un nouvel outil.

pydantic-settings

pydantic-settings permet de charger la configuration de votre application à partir de variables d'environnement, avec la même validation de type. Made in Pydantic, quoi ! Si vous avez suivi l'installation de fastapi[standard] présentée dans cet article, elle est déjà dans vos dépendances : inutile de l'ajouter.

httpx et pytest

FastAPI fournit un client de test qui s'importe via from fastapi.testclient import TestClient et permet de simuler des requêtes sur votre API. Le client repose sur httpx. Si vous avez installé fastapi[standard], httpx est déjà installé.

Le TestClient s'utilise avec pytest, qui, lui, s'installe séparément. Nous avons d'ailleurs un article dédié à pytest.

Pourquoi FastAPI est ... fast ?

Vous avez peut-être croisé des exemples FastAPI utilisant async def au lieu de def. Il n'y a rien de spécifique à FastAPI, c'est de l'asynchrone standard. Le choix entre les deux dépend simplement de ce que votre endpoint fait lui-même.

Si votre endpoint effectue des tâches d'entrées/sorties, déclarez-le en async def.

Dans les autres cas, un endpoint def classique fera l'affaire, FastAPI l'exécute automatiquement dans un thread séparé pour ne jamais bloquer le serveur. Votre fonction n'est pas transformée en fonction asynchrone, elle reste synchrone et s'exécute telle quelle dans son thread. C'est son attente que FastAPI rend asynchrone.

Bravo, tu es prêt à passer à la suite

Rechercher sur le site

Inscris-toi à Docstring

Pour commencer ton apprentissage.

Tu as déjà un compte ? Connecte-toi.