Flask est un framework web pour Python, réputé pour être simple à prendre en main. En plus de vous permettre de concevoir une REST API, il peut aussi se charger de servir la partie cliente de votre application comme par exemple un site Angular.
On utilise normalement un serveur HTTP (comme Nginx pour ne citer que lui) pour servir ce genre de contenu statique de manière bien plus avancée et efficace.
Mais cela peut être intéressant si :
- vous avez un serveur Flask qui expose votre REST API
- vous avez un réel besoin de simplicité au déploiement
- vous pouvez laisser votre serveur absorber cette charge supplémentaire
- vous acceptez l’appartenance forte de votre frontend à votre serveur
- vous avez un intérêt à limiter les couches logicielles.
Comportement voulu
Imaginons une application web étant accessible sur http://localhost:5000 (5000 est le port par défaut de Flask). Il y a donc 2 aspects à correctement faire cohabiter : d’une part la REST API offerte par le serveur Flask et d’autre part l’application Angular.
L’idée est d’avoir le comportement suivant en fonction de l’url appelée :
- http://localhost:5000 : il s’agit de la racine de l’application. Elle doit mener au frontend Angular.
- http://localhost:5000/site : la racine du frontend Angular. Elle permet d’accéder à tout son contenu statique.
- http://localhost:5000/api : la racine de la REST API du serveur. Elle expose les différents services de l’API.
Adaptation de l’application Angular
Cette partie va simplement consister à introduire un chemin relatif au site Angular de sorte à ce qu’il s’attende à fonctionner derrière un suffixe d’url /site
, comme http://localhost:5000/site.
Pour se faire, il suffit de retoucher le fichier angular.json
pour y renseigner notre chemin relatif :
1"projects": {
2 "client": {
3 ...
4 "architect": {
5 "build": {
6 ...
7 "options": {
8 ...
9 "baseHref": "/site/"
10 }
11 ...
On procède ensuite à la collecte des dépendances et au build :
1cd client && npm ci && npm run build
Une fois l’opération terminée, le répertoire dist va contenir notre application Angular prête à être servie par le serveur :
Pour vérifier la prise en compte de notre modification, il suffit de voir le contenu du fichier index.html
qui doit contenir une balise <base href="/site/">
.
1<head>
2 [...]
3 <base href="/site/">
4</head>
Adaptation du serveur Flask
Retouche de la REST API
Comme indiqué plus avant, notre serveur Flask doit gérer l’exposition de l’API et de l’application Angular.
Commençons par ajouter le préfixe /api/
sur toutes les routes de l’API, de sorte à ce que http://localhost:5000/api soit sa racine.
Voici une méthode simple (parmi d’autres) que l’on applique sur l’unique route de notre API:
1@app.route("/api/hello")
2def hello_world():
3 return "Hello, World!"
Démarrons ensuite le serveur Flask pour en vérifier la prise en compte, en commençant par installer les dépendances dans un environnement virtuel Python :
1cd server \
2python3 -m venv ./venv \
3source venv/bin/activate \
4pip3 install -r requirements.txt \
5python -m flask run
En nous rendant ensuite sur http://localhost:5000/api/hello on obtient bien le retour attendu :
Hello, World!
Intégration du site Angular
Nous allons tout d’abord créer deux nouveaux répertoires que l’on va nommer static
et templates
.
Dans le premier répertoire nous allons mettre tout le contenu du frontend issu de client/dist/client/
, à l’exception du fichier index.html
qui lui ira dans le second répertoire.
La suite consiste à créer un Blueprint que l’on va exploiter pour servir le site Angular :
1site_bp = Blueprint('site',
2 __name__,
3 template_folder='templates',
4 static_folder='static',
5 static_url_path='/site'
6 )
template_folder
: chemin relatif vers notre répertoiretemplates
.static_folder
: chemin relatif vers notre répertoirestatic
.static_url_path
: le chemin d’url relatif permettant d’accéder au contenu statique, soit/site
.
Maintenant que le contenu du frontend Angular est connu du serveur, il faut désormais définir une route afin de le servir. Ajoutons ces quelques lignes :
1@site_bp.route('/')
2def serve_site():
3 return render_template('index.html')
4
5app.register_blueprint(site_bp)
Cette route capture tout simplement les requêtes qui ciblent la racine du serveur, de manière à retourner notre fichier index.html
comme un template.
Vérifions que le site apparaît bien en nous rendant sur http://localhost:5000
On est en effet redirigé sur /site
et le site s’affiche.
Jusqu’ici le contrat est remplit, mais il y a un autre cas à gérer : celui on l’on arrive directement sur http://localhost:5000/site. En effet les routes définies sur notre serveur ne connaissent que /
et /api
.
La solution va consister à ajouter cette dernière route :
1@site_bp.route('/')
2def serve_site():
3 return render_template('index.html')
4
5
6@site_bp.route('/<path:text>', methods=['GET'])
7def redirect_to_site(text):
8 if text.startswith('site'):
9 return serve_site()
10 else:
11 abort(404)
12
13app.register_blueprint(site_bp)
Cette nouvelle route capture toutes les requêtes GET
afin d’identifier celles ciblant /site
et déléguer le traitement à serve_site()
pour au final servir index.html
.
Conclusion
Nous avons ainsi vu comment construire une application web minimaliste, dont la particularité est d’être uniquement constituée d’un serveur Flask exposant d’une part une REST API, et d’autre part un frontend Angular.
Certes ce n’est pas une solution pour des applications de production (comme indiqué au début de cet article), mais si vous cherchez quelque chose de minimaliste sans grande prétention, cela peut faire le job.