Grégory Bourguin
SysReIC - LISIC - ULCO
Routage Express

Route

Dans les chapitres précédents, nous avons vu comment construire une réponse du serveur aux requêtes envoyées par un client.

Cependant, mis à part pour les fichiers statiques du dossier /public, les serveurs que nous avons construits envoient toujours la même réponse au client, quelle que soit la requête reçue.

La notion de route permet de différencier le traitement des requêtes en fonction de la méthode utilisée, du chemin demandé et des données éventuelles qu'elles contiennent.

Une route définit un lien entre les propriétés d'une requête et le traitement spécifique qui lui est associé.

La documentation officielle du Routage Express se trouve ici .

Simple GET

Les requêtes GET sont utilisées pour demander au serveur de renvoyer une information spécifique basée sur le chemin de la requête, et éventuellement des paramètres transmis au serveur directement dans l'URL demandée.

La méthode get(chemin, handler) d'Express permet d'ajouter des middlewares qui ne se déclenchent que pour des requêtes GET sur un chemin spécifique.


L'exemple ci-dessous montre comment créer une application Express qui renvoie le message "Bienvenue sur la HOME PAGE" lorsque le serveur reçoit une requête GET sur le chemin "/".

Avec ce serveur, un accès à l'adresse http://127.0.0.1:3000/ renvoie le message "Bienvenue sur la HOME PAGE".

Ce fonctionnement peut sembler très similaire à la 1ère mise en oeuvre d'Express que nous avions faite dans un autre chapitre. Il y a pourtant une grosse différence dans le sens ou ce serveur ne renvoie le message que pour une requête GET sur le chemin "/".

Pour s'en convaincre, il suffit de tester l'accès à une autre URL comme par exemple http://127.0.0.1:3000/truc/machin/chose : si l'ancien serveur renvoyait le message quelle que soit l'URL, celui-ci renvoie une erreur lorsque l'URL demandée ne correspond à aucune route.

Pour traiter d'autres chemins, il faut ajouter d'autres déclarations de routes.
NB : les chemins définis peuvent utiliser des expressions régulières.

NB : le matching sur les routes déclarées est fait séquentiellement dans l'ordre de déclaration des routes jusqu'à ce qu'un pattern soit trouvé.


L'exemple ci-dessous ajoute 2 nouvelles routes :

Redirection

Dans un routage, au lieu de renvoyer directement une réponse, il peut être intéressant de rediriger le navigateur client vers une autre URL.

La méthode redirect de l'objet res permet d'effectuer une redirection.


Dans l'exemple ci-dessous, la route par défaut n'envoie plus un message, mais redirige automatiquement le client vers la home page.

Exercice 04

Dans les exemples précédents, nous avons créé une "home page" utilisant un layout EJS et qui était renvoyée par le serveur quelle que soit la requête reçue.

Cet exercice consiste à créer un site simple sur le même modèle et gérant 2 routes :

  • / : la page d'accueil qui affiche "> Accueil" en haut à gauche et "Bienvenue sur Démo Routage Express" en grand au centre. "
  • /about : une page qui affiche "> À propos" en haut à gauche et "© Copyright 2022 votre_nom" en grand au centre.
  • Toute autre requête doit être redirigée vers la page d'Accueil.

Le header contient une barre de navigation avec 2 liens/boutons permettant d'accéder aux différentes routes du site.

Le site doité être réalisé en utilisant un layout EJS lui-même utilisant des parties séparées pour son header et son footer.

L'image à utiliser sur la page d'accueil se trouve ici.

Design de la page "Accueil" :

Design de la page "À propos" :

La correction se trouve ICI.

Router

Dans un "réel" site web, le nombre de routes (ex. /, /research, /teaching) et sous-routes (ex. /teaching/node, /teaching/node/express, /teaching/javascript, ...) peut être conséquent.

Les traitements correspondants alourdissent le fichier app.js (qui contient en plus tous les middlewares, etc.).

Exemple (simple !) :

app.js

const express = require('express');
const app = express();

// dossier public (pour le css, etc.)
const path = require('path');
app.use(express.static(path.join(__dirname, 'public')));

// définition du view engine
app.set('view engine', 'ejs'); // npm install --save ejs
app.set('views', path.join(__dirname, 'views'));

// layout
const expressLayouts = require('express-ejs-layouts'); //npm install express-ejs-layouts
app.use(expressLayouts);
app.set('layout', '../views/layouts/layout') ; // définit le layout par défaut

// ... il pourrait y a voir plus de choses ici...

// ROUTAGE (les traitements ci-dessous sont très simplifiés pour l'exemple)
app.get("/", (req, res) => {
    res.send('<h1>Bienvenue chez Grégory Bourguin</h1>');
});

app.get("/research", (req, res) => {
    res.send("<h1>Travaux de Recherche</h1>");
});

app.get('/teaching', (req, res) => {
    res.send('<h1>Enseignements</h1>') ;
});

app.get('/teaching/javascript', (req, res) => {
    res.send('<h1>Cours de JavaScript</h1>') ;
});

app.get('/teaching/php', (req, res) => {
    res.send('<h1>Cours de PHP</h1>') ;
});

app.get('/teaching/node', (req, res) => {
    res.send('<h1>Introduction à Node.js</h1>') ;
});

app.get('/teaching/node/express', (req, res) => {
    res.send('<h1>Cours sur le framework Express</h1>') ;
});

app.get('*', (req, res) => {
    res.redirect('/')
});

module.exports = app

Notion de Router

Pour pallier ce problème et séparer les traitements liés à un routage complexe, Express introduit la notion de Router.

L'idée générale est de créer différents routers qui sont chacun chargés de la gestion d'un ensemble de routes.

Chaque router est défini dans un fichier Javascript différent et, par convention, les fichiers routers sont placés dans le dossier /routes du serveur.

Identification des routers

La première étape consiste à identifier les routes "principales" qui correspondent à des routers.

Dans notre exemple, on peut identifier les routes principales suivantes :

  • / : sera gérée par homeRouter.js
  • /research : sera gérée par researchRouter.js
  • /teaching : sera gérée par teachingRouter.js

La délégation du traitement de chaque route "principale" à un router est spécifiée dans le fichier app.js (NB : les fichiers routers seront définis dans l'étape suivante).
Vous remarquerez que chaque router est bien attaché à une route principale spécifique (lignes 19, 22 et 25).

Définition des routers

Il reste à définir le contenu des routers (ce qu'on aurait du faire avant, mais il est important de comprendre qu'un router est attaché à une route principale dans app.js!).


homeRouter.js

Le code de homeRouter est simple du fait qu'il ne gère qu'une seule (sous-)route correspondant à la racine du serveur.


researchRouter.js

Le code de researchRouter demande un peu plus de réflexion.

La route définie ligne 8 spécifie le chemin / : il est important de bien comprendre que dans ce contexte, / signifie la racine du router (et non pas celle du serveur).

Puisque dans app.js (ligne 22), researchRouter est lié au chemin /research, le chemin / défini dans researchRouter (ligne 8) correspond en fait à /research + /, c'est à dire : /research/ (en fait équivalent à http://127.0.0.1:3000/research).


teachingRouter.js

Le code le plus complexe est celui de teachingRouter du fait qu'il spécifie le traitement de nombreuses sous-routes.

Comme précédemment, chaque chemin indiqué dans teachingRouter.js, doit être compris dans le contexte du chemin auquel ce router est lié dans app.js (ligne 25), c'est à dire qu'il est concanténé à la base /teaching.

Ainsi, par exemple, le chemin de routage /node/express défini dans teachingRouter.js ligne 22, correspond en réalité à l'URL : http://127.0.0.1:3000/teaching/node/express .

Exercice 5

L'exemple que nous venons de développer utilise de simple send pour répondre aux requêtes d'un client.

Dupliquez ce serveur et transformez-le pour qu'il réponde avec de "vraies" pages web générées en utilisant un layout et des vues EJS.

Exercice 06

Maintenant que les routers n'ont plus de secret pour vous, dupliquez le serveur que vous avez réalisé dans l'Exercice 04 et transformez-le pour qu'il mette en oeuvre des routers pour ses 2 routes principales, soient / et /about.

NB : ces routers sont très simples, mais cela permettra de vérifier que vous êtes capables de mettre en oeuvre cette nouvelle architecture de serveur par vous-mêmes ;).

Controller

La dernière étape pour se conformer à la convention du routage Express consiste à ajouter la notion de controller.

Un Controller correspond à un fichier Javascript qui exporte un ensemble de fonctions utilisées par un Router.

Le principe est d'externaliser les fonctions de traitement des routers (qui peuvent être très longues).

Par convention, les controllers sont créés dans le dossier /controllers.

Dans notre exemple, nous créons les controllers suivants :

  • homeController
  • researchController
  • teachingController

Fichiers homeController.js et homeRouter.js (modifié) :

Fichiers researchController.js et researchRouter.js (modifié) :

Fichier teachingController.js :
(vous devriez être capables de modifier teachingRouter.js tout seuls :) )

Exercice 07

Dupliquez le serveur que vous avez réalisé dans l'Exercice 06 et modifiez-le pour qu'il mette en oeuvre les controllers adéquats.

La correction se trouve ICI.

Paramètres & Données

Les requêtes envoyées au serveur peuvent parfois (souvent?) contenir des données qui sont utilisées dans leur traitement.

Paramètres de Route

Un paramètre de route correspond à une partie du chemin de l'URL de la requête.

Considérons par exemple l'URL : http://127.0.0.1:3000/user/666

La partie du chemin /666 correspond à l'identifiant de l'utilisateur (par ex. dans la BD) et le handler de requête doit utiliser cette valeur pour fournir les informations sur le bon utilisateur.

Les paramètres sont "déclarés" en utilisant :nom_param dans le pattern de la route.

Pour l'exemple ci-dessus, si on veut nommer le paramètre "id", on utilise le chemin de route /user/:id.

La récupération des paramètres se fait simplement en accédant à l'attribut req.params

Dans notre exemple, on trouvera donc la valeur 666 dans req.params.id.

Et voici un exemple de traîtement :

Exercice 08

Créez l'application et la vue show_route_params utilisée dans l'exemple que nous venons de voir pour que le résultat soit le suivant :

La correction se trouve ICI.
(contient la correction des exercices 8, 9 & 10)

Paramètres GET

Les paramètres d'une requête GET sont envoyés dans l'URL de la requête sous la forme
url?name=value[&name=value] (name est le nom du paramètre transmis et value sa valeur).

Exemple : http://127.0.0.1:3000/?day=9&month=octobre&year=1971.
Cette requête contient 3 paramètres :

  • day = 9
  • month = octobre
  • year = 1971

La récupération des paramètres se fait simplement en accédant à l'attribut req.query

Dans l'URL précédente, on aura par exemple req.query.day qui vaut 9.

Voici un exemple de traitement :

Exercice 09

Complétez l'application de l'Exercice 08 avec le handler de traitement des paramètres GET ci-dessus (sans modifier le render :) ) et créez la vue show_get_params pour que le résultat soit le suivant :

La correction se trouve ICI.
(contient la correction des exercices 8, 9 & 10)

Données POST

Envoi d'un formulaire :

Les données POST proviennent généralement d'un formulaire.

Pour notre exemple, nous allons fournir une vue contenant un formulaire :
-> l'action déclenche la route /form en méthode POST.
-> il y a 3 champs : firstname, lastname et button.
(NB : j'ai utilisé les css Boostrap)

Cette vue peut par exemple envoyée par la route /form en méthode GET :

Traitement du formulaire après soumission :

Les données POST sont contenues dans le corps de la requête.

De ce fait, elles doivent être parsées ce qui rend leur récupération plus complexe.

Express fournit 2 modules permettant d'extraire automatiquement les données du corps d'une requête POST : express.json et express.urlencoded.
Les données sont alors disponibles dans l'attribut req.body.

Les modules doivent être insérés en tant que middlewares (avant les routes) :

Et voici un exemple de traitement des données reçues du formulaire :

Exercice 10

Complétez l'application de l'Exercice 09 avec tout ce que nous venons de voir (vue et route d'envoi du formulaire, route de traitement des données du formulaire après soumission) et créez la vue show_post_data pour que le résultat comme ci-dessous.

Envoi du formulaire :

Traitement du formulaire :

La correction se trouve ICI.
(contient la correction des exercices 8, 9 & 10)