Embarquez une app Angular dans votre serveur Flask

Faites cohabiter une REST API et une application Angular au sein d’un serveur Flask pour obtenir une application web archi-minimaliste.
Embarquez une app Angular dans votre serveur Flask

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 :

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épertoire templates.
  • static_folder : chemin relatif vers notre répertoire static.
  • 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.