FastAPI : créer une API REST moderne en Python en 20 minutes

Guide complet pour créer une API REST ultra-rapide avec FastAPI, le framework Python moderne avec typage automatique, documentation Swagger intégrée et performances exceptionnelles.

FastAPI est le framework Python moderne pour créer des APIs ultra-rapides en 2025. Avec typage automatique, documentation Swagger interactive, validation Pydantic et performances comparables à Node.js, FastAPI a révolutionné le développement d'APIs en Python.

Dans ce tutoriel, tu vas créer une API REST complète en moins de 20 minutes avec :

  • ✅ CRUD complet (Create, Read, Update, Delete)
  • ✅ Validation automatique des données (Pydantic)
  • ✅ Documentation Swagger auto-générée
  • ✅ Base de données SQLite
  • ✅ Authentification JWT
  • ✅ Déploiement production-ready

Pourquoi FastAPI en 2025 ?

FastAPI a été créé par Sebastián Ramírez en 2018 et est devenu le framework Python le plus populaire pour les APIs modernes.

Avantages FastAPI

  • Ultra-rapide : performances comparables à Node.js et Go
  • 🦄 Type hints Python : auto-complétion et validation
  • 📚 Documentation auto-générée : Swagger UI + ReDoc
  • Validation Pydantic : sécurité et robustesse
  • 🔄 Async natif : support async/await pour haute performance
  • 🛠️ Prêt production : utilisé par Microsoft, Uber, Netflix
  • 🐍 Python moderne : Python 3.8+ requis

Installation

Prérequis

# Python 3.8+ requis
python --version  # Python 3.11+ recommandé (2025)

# Créer un environnement virtuel
python -m venv venv

# Activer (Linux/macOS)
source venv/bin/activate

# Activer (Windows)
venv\Scripts\activate

Installer FastAPI + Uvicorn

# Uvicorn = serveur ASGI ultra-rapide
pip install "fastapi[all]"

# OU installation minimale
pip install fastapi uvicorn[standard]

Hello World FastAPI

# main.py
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def read_root():
    return {"message": "Hello World"}

@app.get("/items/{item_id}")
def read_item(item_id: int, q: str = None):
    return {"item_id": item_id, "q": q}

# Lancer : uvicorn main:app --reload

Tester l'API

# Démarrer le serveur
uvicorn main:app --reload

# Ouvrir dans le navigateur
http://localhost:8000           # API
http://localhost:8000/docs      # Swagger UI (interactive)
http://localhost:8000/redoc     # ReDoc (documentation)

Magie de FastAPI : La documentation Swagger est auto-générée grâce aux type hints ! 🎉


Projet complet : Todo List API

Créons une API complète pour gérer des tâches (todos).

Structure du projet

fastapi-todo/
├── venv/
├── app/
│   ├── __init__.py
│   ├── main.py
│   ├── models.py      # Modèles Pydantic
│   ├── database.py    # SQLite + SQLAlchemy
│   └── auth.py        # JWT authentification
├── requirements.txt
└── .env

Étape 1 : Modèles Pydantic

# app/models.py
from pydantic import BaseModel, Field
from typing import Optional
from datetime import datetime

class TodoBase(BaseModel):
    title: str = Field(..., min_length=1, max_length=100)
    description: Optional[str] = Field(None, max_length=500)
    completed: bool = False

class TodoCreate(TodoBase):
    pass

class TodoUpdate(BaseModel):
    title: Optional[str] = Field(None, min_length=1, max_length=100)
    description: Optional[str] = None
    completed: Optional[bool] = None

class TodoResponse(TodoBase):
    id: int
    created_at: datetime

    class Config:
        from_attributes = True  # Anciennement orm_mode

Pydantic valide automatiquement toutes les données entrantes. Si title manque, FastAPI renvoie une erreur 422 avec détails !


Étape 2 : Base de données SQLite

# app/database.py
from sqlalchemy import create_engine, Column, Integer, String, Boolean, DateTime
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from datetime import datetime

# SQLite (fichier local)
SQLALCHEMY_DATABASE_URL = "sqlite:///./todos.db"

engine = create_engine(
    SQLALCHEMY_DATABASE_URL,
    connect_args={"check_same_thread": False}
)

SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()

# Modèle SQLAlchemy
class TodoDB(Base):
    __tablename__ = "todos"

    id = Column(Integer, primary_key=True, index=True)
    title = Column(String, nullable=False)
    description = Column(String, nullable=True)
    completed = Column(Boolean, default=False)
    created_at = Column(DateTime, default=datetime.utcnow)

# Créer la table
Base.metadata.create_all(bind=engine)

# Dépendance pour les requêtes
def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

Étape 3 : API CRUD complète

# app/main.py
from fastapi import FastAPI, Depends, HTTPException, status
from sqlalchemy.orm import Session
from typing import List

from .database import get_db, TodoDB
from .models import TodoCreate, TodoUpdate, TodoResponse

app = FastAPI(
    title="Todo API",
    description="API de gestion de tâches avec FastAPI",
    version="1.0.0"
)

# CREATE - Créer une tâche
@app.post("/todos/", response_model=TodoResponse, status_code=status.HTTP_201_CREATED)
def create_todo(todo: TodoCreate, db: Session = Depends(get_db)):
    db_todo = TodoDB(**todo.model_dump())
    db.add(db_todo)
    db.commit()
    db.refresh(db_todo)
    return db_todo

# READ ALL - Lister toutes les tâches
@app.get("/todos/", response_model=List[TodoResponse])
def read_todos(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
    todos = db.query(TodoDB).offset(skip).limit(limit).all()
    return todos

# READ ONE - Lire une tâche par ID
@app.get("/todos/{todo_id}", response_model=TodoResponse)
def read_todo(todo_id: int, db: Session = Depends(get_db)):
    todo = db.query(TodoDB).filter(TodoDB.id == todo_id).first()
    if not todo:
        raise HTTPException(status_code=404, detail="Todo not found")
    return todo

# UPDATE - Modifier une tâche
@app.put("/todos/{todo_id}", response_model=TodoResponse)
def update_todo(todo_id: int, todo_update: TodoUpdate, db: Session = Depends(get_db)):
    db_todo = db.query(TodoDB).filter(TodoDB.id == todo_id).first()
    if not db_todo:
        raise HTTPException(status_code=404, detail="Todo not found")

    # Mettre à jour seulement les champs fournis
    update_data = todo_update.model_dump(exclude_unset=True)
    for key, value in update_data.items():
        setattr(db_todo, key, value)

    db.commit()
    db.refresh(db_todo)
    return db_todo

# DELETE - Supprimer une tâche
@app.delete("/todos/{todo_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_todo(todo_id: int, db: Session = Depends(get_db)):
    db_todo = db.query(TodoDB).filter(TodoDB.id == todo_id).first()
    if not db_todo:
        raise HTTPException(status_code=404, detail="Todo not found")

    db.delete(db_todo)
    db.commit()
    return None

# Health check
@app.get("/health")
def health_check():
    return {"status": "healthy"}

Lancer l'API

# Lancer avec hot reload
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000

# Tester avec curl
curl -X POST http://localhost:8000/todos/ \
  -H "Content-Type: application/json" \
  -d '{"title": "Apprendre FastAPI", "description": "Suivre le tutoriel CODAURA"}'

# Lister toutes les tâches
curl http://localhost:8000/todos/

# Documentation interactive
http://localhost:8000/docs

Documentation Swagger interactive

FastAPI génère automatiquement une interface Swagger UI à /docs où tu peux :

  • ✅ Tester toutes les routes directement dans le navigateur
  • ✅ Voir les modèles de données (schemas)
  • ✅ Voir les codes de réponse et exemples
  • ✅ Exporter la spec OpenAPI 3.0

Aucune configuration nécessaire : tout est inféré depuis tes type hints Python ! 🚀


Validation automatique avec Pydantic

Exemple de requête invalide :

# POST /todos/ avec title vide
{
  "title": "",
  "description": "Test"
}

# Réponse 422 Unprocessable Entity
{
  "detail": [
    {
      "type": "string_too_short",
      "loc": ["body", "title"],
      "msg": "String should have at least 1 character",
      "input": ""
    }
  ]
}

FastAPI + Pydantic bloquent automatiquement les données invalides. Sécurité maximale ! 🔒


Authentification JWT

Installation

pip install python-jose[cryptography] passlib[bcrypt] python-multipart

app/auth.py

# app/auth.py
from datetime import datetime, timedelta
from typing import Optional
from jose import JWTError, jwt
from passlib.context import CryptContext
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm

SECRET_KEY = "your-secret-key-change-in-production"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

# Hash password
def hash_password(password: str) -> str:
    return pwd_context.hash(password)

# Vérifier password
def verify_password(plain_password: str, hashed_password: str) -> bool:
    return pwd_context.verify(plain_password, hashed_password)

# Créer JWT token
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
    to_encode = data.copy()
    expire = datetime.utcnow() + (expires_delta or timedelta(minutes=15))
    to_encode.update({"exp": expire})
    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt

# Vérifier JWT token
def verify_token(token: str = Depends(oauth2_scheme)):
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username: str = payload.get("sub")
        if username is None:
            raise HTTPException(status_code=401, detail="Invalid token")
        return username
    except JWTError:
        raise HTTPException(status_code=401, detail="Invalid token")

Protéger les routes

# app/main.py
from .auth import create_access_token, verify_token
from fastapi.security import OAuth2PasswordRequestForm

# Login endpoint
@app.post("/token")
def login(form_data: OAuth2PasswordRequestForm = Depends()):
    # Vérifier identifiants (exemple simplifié)
    if form_data.username != "admin" or form_data.password != "secret":
        raise HTTPException(status_code=401, detail="Invalid credentials")

    access_token = create_access_token(data={"sub": form_data.username})
    return {"access_token": access_token, "token_type": "bearer"}

# Route protégée
@app.get("/protected")
def protected_route(username: str = Depends(verify_token)):
    return {"message": f"Hello {username}, you are authenticated!"}

Tester l'authentification

# 1. Obtenir un token
curl -X POST http://localhost:8000/token \
  -d "username=admin&password=secret"

# Réponse : {"access_token": "eyJhbGc...", "token_type": "bearer"}

# 2. Utiliser le token
curl http://localhost:8000/protected \
  -H "Authorization: Bearer eyJhbGc..."

Variables d'environnement

# .env
DATABASE_URL=sqlite:///./todos.db
SECRET_KEY=change-me-in-production
ENVIRONMENT=development

# app/config.py
from pydantic_settings import BaseSettings

class Settings(BaseSettings):
    database_url: str
    secret_key: str
    environment: str

    class Config:
        env_file = ".env"

settings = Settings()

Tests avec pytest

# test_main.py
from fastapi.testclient import TestClient
from app.main import app

client = TestClient(app)

def test_create_todo():
    response = client.post(
        "/todos/",
        json={"title": "Test todo", "description": "Test"}
    )
    assert response.status_code == 201
    data = response.json()
    assert data["title"] == "Test todo"
    assert "id" in data

def test_read_todos():
    response = client.get("/todos/")
    assert response.status_code == 200
    assert isinstance(response.json(), list)

# Lancer : pytest

Performance : FastAPI vs Flask vs Django

FrameworkReq/secLatenceType hintsAsync
FastAPI25 0004ms✅ Natif✅ Oui
Flask8 00012ms❌ Non⚠️ Extension
Django REST5 00020ms❌ Non⚠️ Récent

FastAPI est 3x plus rapide que Flask !


Déploiement production

Dockerfile

# Dockerfile
FROM python:3.11-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY ./app ./app

CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

Docker Compose avec PostgreSQL

# docker-compose.yml
version: '3.8'

services:
  api:
    build: .
    ports:
      - "8000:8000"
    environment:
      - DATABASE_URL=postgresql://user:pass@db:5432/tododb
    depends_on:
      - db

  db:
    image: postgres:15
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
      POSTGRES_DB: tododb
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  postgres_data:

Lancer avec Docker

docker-compose up --build

Déployer sur Render (gratuit)

# 1. Créer requirements.txt
pip freeze > requirements.txt

# 2. Créer render.yaml
services:
  - type: web
    name: fastapi-todo
    env: python
    buildCommand: pip install -r requirements.txt
    startCommand: uvicorn app.main:app --host 0.0.0.0 --port $PORT

# 3. Push sur GitHub et connecter Render

Best practices FastAPI 2025

  • Utilise async/await pour I/O (database, API calls)
  • Sépare la logique : routes, models, database, services
  • Valide avec Pydantic : tous les inputs et outputs
  • Utilise des dépendances : Depends() pour DRY
  • Gère les erreurs proprement : HTTPException avec détails
  • Teste avec pytest : TestClient intégré
  • Documente : Swagger auto mais ajoute descriptions
  • Logs structurés : utilise structlog ou loguru
  • Rate limiting : slowapi pour éviter abus
  • CORS configuré : pour frontend externe

Exemple async avec base de données

# app/database_async.py
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker

DATABASE_URL = "sqlite+aiosqlite:///./todos.db"

engine = create_async_engine(DATABASE_URL)
async_session = sessionmaker(
    engine, class_=AsyncSession, expire_on_commit=False
)

async def get_db():
    async with async_session() as session:
        yield session

# Route async
@app.get("/todos/")
async def read_todos(db: AsyncSession = Depends(get_db)):
    result = await db.execute(select(TodoDB))
    todos = result.scalars().all()
    return todos

CORS pour frontend React/Vue

# app/main.py
from fastapi.middleware.cors import CORSMiddleware

app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:3000"],  # Frontend URL
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

FastAPI vs alternatives

CritèreFastAPIFlaskDjangoExpress.js
Performance⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
Type safety✅ Natif❌ Non❌ Non⚠️ TS
Validation✅ Auto❌ Manuel✅ Forms⚠️ Joi
Documentation✅ Auto❌ Manuel⚠️ DRF❌ Swagger
Async✅ Natif⚠️ Extension⚠️ Récent✅ Natif
Courbe d'apprentissageFacileTrès facileDifficileFacile

Quand utiliser FastAPI ?

✅ Utilise FastAPI si :

  • ✅ Nouvelle API REST/GraphQL en Python
  • ✅ Performance critique (microservices, IoT)
  • ✅ Machine Learning API (intégration facile TensorFlow/PyTorch)
  • ✅ Besoin de documentation auto-générée
  • ✅ Type safety importante

⚠️ Utilise Flask/Django si :

  • ⚠️ Application web complète (templates, admin, ORM complexe) → Django
  • ⚠️ Projet legacy Flask existant → pas de migration
  • ⚠️ Équipe débutante Python → Flask plus simple

Ressources pour aller plus loin


Conclusion

En 2025, FastAPI est LE framework de référence pour créer des APIs modernes en Python. Avec validation automatique, documentation Swagger, performances exceptionnelles et support async natif, FastAPI offre la meilleure developer experience du marché Python.

Ce que tu as appris :

  • ✅ Créer une API REST complète (CRUD)
  • ✅ Validation automatique avec Pydantic
  • ✅ Authentification JWT
  • ✅ Base de données SQLite/PostgreSQL
  • ✅ Documentation Swagger auto-générée
  • ✅ Tests et déploiement

Prochaines étapes :

  • 🔄 Ajouter WebSockets pour temps réel
  • 📊 Intégrer une API ML (scikit-learn, TensorFlow)
  • 🔍 Implémenter une recherche full-text (Elasticsearch)
  • 📈 Ajouter monitoring (Prometheus + Grafana)
  • 🚀 Déployer sur AWS/GCP avec Kubernetes

FastAPI combine la simplicité de Flask avec les performances de Node.js et la rigueur de TypeScript. Si tu crées une API en Python en 2025, FastAPI est le choix évident. 🚀

À toi de jouer ! Crée ta première API FastAPI et découvre pourquoi des milliers de développeurs ont adopté ce framework révolutionnaire. 🐍⚡