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
| Framework | Req/sec | Latence | Type hints | Async |
|---|---|---|---|---|
| FastAPI | 25 000 | 4ms | ✅ Natif | ✅ Oui |
| Flask | 8 000 | 12ms | ❌ Non | ⚠️ Extension |
| Django REST | 5 000 | 20ms | ❌ 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ère | FastAPI | Flask | Django | Express.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'apprentissage | Facile | Très facile | Difficile | Facile |
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
- 🌐 Documentation officielle FastAPI
- 🎓 Tutorial complet FastAPI
- 🐙 GitHub FastAPI (70k+ ⭐)
- 📚 Pydantic Documentation
- 💬 Discord FastAPI
- 🎥 YouTube FastAPI
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. 🐍⚡