📌 Article principal de la série : Python : langage, écosystème et frameworks pour développeurs
Ce tutoriel fait partie de la série Python. Pour les bases du langage et l’installation, consultez l’article principal et le tutoriel d’installation.
Flask est l’un des micro-frameworks web Python les plus populaires au monde. Sa philosophie est simple : fournir les mécanismes essentiels d’une application web (routing d’URL, traitement des requêtes HTTP, rendu de réponses) sans imposer de structure ni de dépendances supplémentaires. Ce tutoriel construit une API REST fonctionnelle de A à Z avec Flask 3.1 : création du projet, définition des routes CRUD, validation des données, gestion des erreurs et test avec curl. À la fin de ce guide, vous disposerez d’une API opérationnelle servant des données JSON, structurée selon les bonnes pratiques.
Prérequis
- Python 3.12 ou 3.13 installé (voir Installer Python et configurer son environnement)
- Connaissance minimale de Python (variables, fonctions, listes, dictionnaires)
- Un terminal et un éditeur de code (VS Code recommandé)
- curl installé pour tester les endpoints (inclus par défaut sur Linux, macOS, et Windows 10+)
- Niveau : débutant à intermédiaire
- Temps estimé : 45 à 60 minutes
Étape 1 — Créer le projet et installer Flask
Chaque projet Flask doit vivre dans son propre environnement virtuel pour isoler ses dépendances. Commencez par créer le dossier du projet et initialiser l’environnement virtuel, exactement comme décrit dans le tutoriel d’installation :
mkdir api-flask-demo
cd api-flask-demo
# Créer l'environnement virtuel
python3.13 -m venv .venv
# Activer l'environnement virtuel
source .venv/bin/activate # Linux / macOS
# .venv\Scripts\Activate.ps1 # Windows PowerShell
# Vérifier que le venv est actif (le prompt doit afficher (.venv))
which python # Doit pointer vers .venv/bin/python
Une fois l’environnement virtuel activé, le prompt de votre terminal affiche (.venv) en préfixe. C’est le signal que toutes les installations pip iront dans ce répertoire isolé. La commande which python (ou where python sur Windows) confirme que l’interpréteur utilisé est bien celui du venv et non le Python global du système.
# Installer Flask 3.1 (version stable actuelle)
pip install flask
# Vérifier la version installée
python -c "import flask; print(flask.__version__)"
# Geler les dépendances
pip freeze > requirements.txt
La commande pip install flask installe Flask et ses dépendances directes : Werkzeug (gestion WSGI, routing, utilitaires HTTP), Jinja2 (moteur de templates), Click (interface ligne de commande de la commande flask), itsdangerous (sécurisation des cookies et tokens) et MarkupSafe (échappement HTML). La commande de vérification doit afficher 3.1.0 ou une version supérieure. pip freeze > requirements.txt capture l’état exact des dépendances — versionnez ce fichier avec votre code.
Étape 2 — Créer la structure du projet
Flask n’impose pas de structure de projet particulière, mais une organisation claire dès le départ facilite la maintenance. Pour une API REST simple, voici la structure recommandée :
api-flask-demo/
├── .venv/ # Environnement virtuel (ignoré par git)
├── app.py # Point d'entrée de l'application
├── requirements.txt # Dépendances épinglées
└── .gitignore # Fichiers à exclure du versioning
Pour des projets plus larges, Flask supporte les Blueprints pour organiser le code en modules, mais pour ce tutoriel introductif, un fichier unique app.py suffit et permet de rester focalisé sur les concepts essentiels. Créez le fichier .gitignore adapté Python si ce n’est pas encore fait, puis créez le fichier principal app.py :
# app.py
from flask import Flask, jsonify, request, abort
app = Flask(__name__)
# Base de données en mémoire (dictionnaire pour ce tutoriel)
# En production, utiliser une vraie base de données (PostgreSQL, SQLite, etc.)
produits = {
1: {"id": 1, "nom": "Laptop ThinkPad", "prix": 850000, "stock": 10},
2: {"id": 2, "nom": "Souris USB", "prix": 15000, "stock": 50},
3: {"id": 3, "nom": "Clé USB 64GB", "prix": 8000, "stock": 200},
}
prochain_id = 4 # Compteur auto-incrémenté (remplace un SERIAL SQL)
if __name__ == "__main__":
app.run(debug=True, host="0.0.0.0", port=5000)
Ce fichier constitue le squelette de l’application. L’objet app = Flask(__name__) est l’instance centrale de l’application Flask — __name__ est le nom du module Python courant, utilisé par Flask pour localiser les ressources statiques et les templates. Le dictionnaire produits joue le rôle de base de données en mémoire pour ce tutoriel — les données sont perdues au redémarrage. Le bloc if __name__ == "__main__" lance le serveur de développement intégré de Flask uniquement quand le fichier est exécuté directement, pas quand il est importé par un autre module.
Étape 3 — Créer la route GET : lister tous les produits
La première route à implémenter est le listing complet des ressources — c’est l’opération de lecture la plus simple et permet de valider que l’API répond correctement. En REST, une requête GET sur la collection (/api/produits) retourne la liste de toutes les ressources.
# Ajouter dans app.py, avant le bloc if __name__ == "__main__"
@app.route("/api/produits", methods=["GET"])
def lister_produits():
"""Retourne la liste complète des produits au format JSON."""
# Convertir le dictionnaire en liste pour la réponse
liste = list(produits.values())
return jsonify({
"succes": True,
"total": len(liste),
"produits": liste
}), 200
Le décorateur @app.route("/api/produits", methods=["GET"]) enregistre la fonction lister_produits comme gestionnaire des requêtes GET sur le chemin /api/produits. La fonction jsonify() convertit un dictionnaire Python en réponse HTTP avec le Content-Type application/json et l’encodage UTF-8 automatiquement géré. Le code HTTP 200 (OK) est retourné explicitement comme second élément du tuple. Testez dès maintenant en lançant le serveur :
# Dans un terminal avec le venv activé
python app.py
# Dans un second terminal, tester avec curl
curl -s http://localhost:5000/api/produits | python3 -m json.tool
La commande python app.py démarre le serveur Flask en mode debug sur le port 5000. Le mode debug active le rechargement automatique lors des modifications du code source et affiche les traceback d’erreur dans le navigateur — à désactiver impérativement en production. curl -s effectue la requête HTTP silencieusement (sans les infos de progression), et le pipe vers python3 -m json.tool formate le JSON pour une lecture facile. La réponse attendue est un objet JSON avec "succes": true et un tableau de 3 produits.
Étape 4 — Créer la route GET par ID : récupérer un produit
La deuxième route REST fondamentale est la récupération d’une ressource unique par son identifiant. Flask permet de capturer des segments variables dans l’URL via la syntaxe <type:nom>, ce qui évite d’avoir à parser l’URL manuellement.
@app.route("/api/produits/<int:produit_id>", methods=["GET"])
def obtenir_produit(produit_id):
"""Retourne un produit spécifique par son ID, ou 404 si introuvable."""
produit = produits.get(produit_id)
if produit is None:
abort(404) # Flask retourne une réponse 404 automatiquement
return jsonify({"succes": True, "produit": produit}), 200
Le paramètre <int:produit_id> dans l’URL indique à Flask que ce segment doit être converti en entier (int) avant d’être passé à la fonction. Si l’URL contient une valeur non convertible en entier (par exemple /api/produits/abc), Flask retourne automatiquement un 404 sans même appeler la fonction. La méthode produits.get(produit_id) retourne None si la clé n’existe pas — plus sûre que produits[produit_id] qui lèverait un KeyError. La fonction abort(404) interrompt immédiatement la fonction et déclenche la réponse d’erreur 404. Testez :
# Produit existant
curl -s http://localhost:5000/api/produits/1 | python3 -m json.tool
# Produit inexistant
curl -s -o /dev/null -w "%{http_code}" http://localhost:5000/api/produits/999
La première commande doit retourner les détails du produit avec ID 1. La seconde commande affiche uniquement le code HTTP retourné — vous devez voir 404, confirmant que Flask gère correctement les ressources inexistantes.
Étape 5 — Créer la route POST : ajouter un produit
La route POST permet de créer une nouvelle ressource. Elle lit les données envoyées par le client dans le corps de la requête HTTP au format JSON, valide les champs requis, puis insère le nouveau produit dans la collection.
@app.route("/api/produits", methods=["POST"])
def creer_produit():
"""Crée un nouveau produit à partir des données JSON reçues."""
global prochain_id
# Lire le corps JSON de la requête
donnees = request.get_json(silent=True)
# Validation : le corps doit être du JSON valide
if donnees is None:
return jsonify({"succes": False, "erreur": "Corps JSON invalide ou absent"}), 400
# Validation : les champs requis doivent être présents
champs_requis = ["nom", "prix", "stock"]
champs_manquants = [c for c in champs_requis if c not in donnees]
if champs_manquants:
return jsonify({
"succes": False,
"erreur": f"Champs manquants : {', '.join(champs_manquants)}"
}), 422
# Validation des types
if not isinstance(donnees["prix"], (int, float)) or donnees["prix"] <= 0:
return jsonify({"succes": False, "erreur": "Le prix doit être un nombre positif"}), 422
# Créer le nouveau produit
nouveau_produit = {
"id": prochain_id,
"nom": str(donnees["nom"]).strip(),
"prix": float(donnees["prix"]),
"stock": int(donnees.get("stock", 0))
}
produits[prochain_id] = nouveau_produit
prochain_id += 1
return jsonify({"succes": True, "produit": nouveau_produit}), 201
La méthode request.get_json(silent=True) parse le corps de la requête en JSON et retourne None (sans lever d’exception) si le parsing échoue. Le paramètre silent=True est essentiel pour un contrôle propre des erreurs. La validation des champs manquants via une liste de compréhension est un pattern Pythonique élégant. Le code HTTP 201 Created (et non 200 OK) signale au client qu’une ressource a été créée — c’est la convention REST standard. Testez la création :
# Créer un nouveau produit
curl -s -X POST http://localhost:5000/api/produits \
-H "Content-Type: application/json" \
-d '{"nom": "Clavier mécanique", "prix": 45000, "stock": 25}' \
| python3 -m json.tool
# Tester la validation : champ manquant
curl -s -X POST http://localhost:5000/api/produits \
-H "Content-Type: application/json" \
-d '{"nom": "Test"}' \
| python3 -m json.tool
La première commande doit retourner le produit créé avec "id": 4 et le code HTTP 201. La seconde doit retourner une erreur 422 avec le message indiquant les champs manquants prix, stock. Si vous obtenez "succes": false avec « Corps JSON invalide », vérifiez que vous incluez bien l’en-tête -H "Content-Type: application/json" — Flask ne parse le corps en JSON que si cet en-tête est présent (ou si force=True est passé à get_json).
Étape 6 — Créer la route PUT : modifier un produit
La route PUT permet de mettre à jour une ressource existante. En REST strict, PUT remplace entièrement la ressource ; PATCH effectue une mise à jour partielle. Pour ce tutoriel, on implémente une mise à jour partielle (comportement PATCH) tout en utilisant PUT — une simplification courante dans les APIs pragmatiques.
@app.route("/api/produits/<int:produit_id>", methods=["PUT"])
def modifier_produit(produit_id):
"""Met à jour les champs fournis d'un produit existant."""
produit = produits.get(produit_id)
if produit is None:
abort(404)
donnees = request.get_json(silent=True)
if donnees is None:
return jsonify({"succes": False, "erreur": "Corps JSON invalide"}), 400
# Mise à jour sélective : ne modifier que les champs fournis
if "nom" in donnees:
produit["nom"] = str(donnees["nom"]).strip()
if "prix" in donnees:
if not isinstance(donnees["prix"], (int, float)) or donnees["prix"] <= 0:
return jsonify({"succes": False, "erreur": "Prix invalide"}), 422
produit["prix"] = float(donnees["prix"])
if "stock" in donnees:
produit["stock"] = int(donnees["stock"])
return jsonify({"succes": True, "produit": produit}), 200
La mise à jour sélective avec if "nom" in donnees permet au client d’envoyer uniquement les champs à modifier sans écraser les autres. Les dictionnaires Python étant passés par référence, la modification directe de produit modifie la valeur stockée dans le dictionnaire produits — pas de réaffectation nécessaire. Testez la mise à jour :
curl -s -X PUT http://localhost:5000/api/produits/1 \
-H "Content-Type: application/json" \
-d '{"prix": 920000}' \
| python3 -m json.tool
La réponse doit montrer le produit 1 avec le prix mis à jour à 920000, les autres champs (nom, stock) inchangés. Le code de retour est 200 OK puisque la ressource existait et a été modifiée avec succès.
Étape 7 — Créer la route DELETE : supprimer un produit
La route DELETE supprime une ressource. La convention REST retourne un code 200 avec confirmation, ou 204 No Content sans corps de réponse. La version avec corps JSON est préférable pour les APIs publiques car elle fournit une confirmation explicite.
@app.route("/api/produits/<int:produit_id>", methods=["DELETE"])
def supprimer_produit(produit_id):
"""Supprime un produit par son ID."""
produit = produits.pop(produit_id, None)
if produit is None:
abort(404)
return jsonify({
"succes": True,
"message": f"Produit #{produit_id} supprimé",
"produit_supprime": produit
}), 200
La méthode dict.pop(key, default) supprime et retourne la valeur associée à la clé, ou retourne default si la clé n’existe pas — évitant un double accès au dictionnaire (get puis del). La réponse inclut le produit supprimé, ce qui permet au client de confirmer qu’il a supprimé la bonne ressource.
Étape 8 — Gérer les erreurs globalement
Flask permet d’enregistrer des gestionnaires d’erreurs globaux avec le décorateur @app.errorhandler. Sans eux, les erreurs 404 et 500 retournent du HTML par défaut — incompatible avec une API JSON dont les clients attendent toujours du JSON, quelle que soit l’erreur.
@app.errorhandler(404)
def non_trouve(erreur):
return jsonify({"succes": False, "erreur": "Ressource introuvable"}), 404
@app.errorhandler(405)
def methode_non_autorisee(erreur):
return jsonify({"succes": False, "erreur": "Méthode HTTP non autorisée"}), 405
@app.errorhandler(500)
def erreur_interne(erreur):
return jsonify({"succes": False, "erreur": "Erreur interne du serveur"}), 500
Ces trois gestionnaires couvrent les cas d’erreur les plus fréquents d’une API REST. Ils assurent que même les erreurs inattendues retournent du JSON au lieu de HTML, garantissant une interface cohérente pour les clients de l’API. Testez que le 404 personnalisé fonctionne :
curl -s http://localhost:5000/api/inexistant | python3 -m json.tool
La réponse doit être {"erreur": "Ressource introuvable", "succes": false} au lieu de la page d’erreur HTML par défaut de Flask — confirmant que les gestionnaires d’erreurs globaux sont bien actifs.
Étape 9 — Vérification finale : tester toutes les routes
Avant de considérer l’API comme fonctionnelle, effectuez un test complet de toutes les routes pour valider le comportement attendu end-to-end :
BASE="http://localhost:5000/api/produits"
echo "=== GET tous les produits ==="
curl -s $BASE | python3 -m json.tool
echo "=== GET produit #2 ==="
curl -s $BASE/2 | python3 -m json.tool
echo "=== POST nouveau produit ==="
curl -s -X POST $BASE \
-H "Content-Type: application/json" \
-d '{"nom":"Moniteur 24 pouces","prix":180000,"stock":8}' \
| python3 -m json.tool
echo "=== PUT modifier prix produit #1 ==="
curl -s -X PUT $BASE/1 \
-H "Content-Type: application/json" \
-d '{"prix":880000}' \
| python3 -m json.tool
echo "=== DELETE produit #3 ==="
curl -s -X DELETE $BASE/3 | python3 -m json.tool
echo "=== GET vérification après DELETE ==="
curl -s $BASE | python3 -m json.tool
Ce script de test enchaîne les 5 opérations CRUD dans l’ordre logique : listing initial, lecture d’un élément, création, mise à jour, suppression, puis listing final pour vérifier l’état de la collection. La sortie finale doit montrer 3 produits (le produit #3 supprimé, le nouveau produit #4 ajouté) avec le produit #1 au prix mis à jour. Si un test échoue, relisez la section correspondante et vérifiez que le serveur Flask est toujours en cours d’exécution dans l’autre terminal.
Erreurs fréquentes
| Erreur | Cause | Solution |
|---|---|---|
ImportError: No module named 'flask' |
Flask installé hors du venv actif | Activer le venv (source .venv/bin/activate) puis réinstaller |
| POST retourne « Corps JSON invalide » | Header Content-Type: application/json absent dans curl |
Ajouter -H "Content-Type: application/json" |
| Port 5000 déjà utilisé (macOS) | AirPlay Receiver occupe le port 5000 sur macOS Monterey+ | Changer le port : app.run(port=5001) |
| Les modifications ne sont pas rechargées | Mode debug non activé | Vérifier debug=True dans app.run() |
| Données perdues après redémarrage | Stockage en mémoire (dictionnaire Python) | Normal pour ce tutoriel — utiliser SQLAlchemy + SQLite pour persister |
Tutoriels frères
- Installer Python 3 et configurer son environnement de développement — Prérequis de ce tutoriel : installation Python, venv, VS Code
Pour aller plus loin
- 🔝 Retour à l’article principal : Python : langage, écosystème et frameworks pour développeurs
- Documentation officielle Flask 3.1
- Référence Flask.route — décorateur de routing
- Documentation Werkzeug — la bibliothèque WSGI sous Flask
- Flask-SQLAlchemy — extension ORM pour Flask (prochaine étape naturelle)
FAQ
- Quelle différence entre Flask et FastAPI pour une API REST ?
- Flask est synchrone par défaut (supporte async depuis la v2.0 mais de façon optionnelle) et ne fait pas de validation automatique des données. FastAPI est asynchrone natif, valide les données via Pydantic et génère automatiquement la documentation Swagger/OpenAPI. Pour une API simple et rapide à mettre en place, Flask est excellent. Pour une API de production avec typage strict et documentation automatique, FastAPI est le meilleur choix en 2024.
- Comment passer Flask en production ?
- Le serveur de développement Flask (
app.run(debug=True)) ne doit jamais être utilisé en production. En production, Flask doit être servi par un serveur WSGI robuste comme Gunicorn (gunicorn app:app) ou uWSGI, derrière un reverse proxy Nginx ou Caddy qui gère le SSL/TLS, la compression et les fichiers statiques. - Comment ajouter une vraie base de données à cette API ?
- L’étape naturelle suivante est d’intégrer Flask-SQLAlchemy avec SQLite (développement) ou PostgreSQL (production). Flask-SQLAlchemy fournit un ORM qui mappe les tables de la base de données en classes Python. Les opérations de création/lecture/mise à jour/suppression deviennent des appels à
db.session.add(),db.session.commit(),Model.query.get(id)— remplaçant le dictionnaire en mémoire de ce tutoriel.