Apprendre Express.js : Routage
Dans Express, toute requête HTTP passe par le routeur qui va définir qui est en charge du traitement de la requête.
Structure d’une route
Dans Express, une route est définie selon la structure suivante :
app.method(path, handler);
app
est une instance d’Expressmethod
est une méthode HTTPpath
est le chemin demandé par le clienthandler
est la fonction exécutée lorsque la route est mise en correspondance
La route suivante affiche par exemple "Hello World!"
lorsqu’un utilisateur accède à localhost:3000/hello
via la méthode HTTP GET
.
app.get('/hello', (req, res) => {
res.send('Hello World!');
});
Méthodes
Les méthodes de routages correspondent aux méthodes HTTP classiques. Les plus connues sont les get
et post
, mais il est également tout à fait possible de créer des routes en put
, patch
ou encore en delete
et même de nombreuses autres méthodes plus spécifiques.
GET
Il s’agit de la méthode utilisée pour récupérer une ressource.
app.get('/path', (req, res) => {
// ...
});
POST
Il s’agit de la méthode utilisée pour envoyer des données à traiter à une source spécifiée. Cet envoi de données est principalement destiné à enregistrer des données.
app.post('/path', (req, res) => {
// ...
});
PUT & PATCH
Il s’agit de la méthode utilisée pour créer une nouvelle ressource ou en remplacer une.
En cas de remplacement avec PUT
, il faudra transmettre l’intégralité des champs de la ressource, à la différence de PATCH
qui permet de ne transmettre que le champ spécifique à modifier sur la ressource.
app.put('/path', (req, res) => {
// ...
})
app.patch('/path', (req, res) => {
// ...
})
DELETE
Il s’agit de la méthode utilisée pour supprimer une ressource spécifiée.
app.delete('/path', (req, res) => {
// ...
})
ALL
Dans Express, il existe une méthode de routage spéciale, app.all()
, qui n’est pas dérivée d’une méthode HTTP. Cette méthode répondra à la requête quelle que soit la méthode HTTP de demande (GET
, POST
, PUT
, PATCH
, DELETE
ou toute autre méthode de demande HTTP prise en charge dans le module http).
app.all('/path', (req, res) => {
// ...
})
Chemin
Le chemin de routage correspond à la partie de l’URL succédant au nom de domaine. C’est lui qui est analysé par le serveur afin de savoir quelle ressource retourner.
Chemin statique
Le chemin peut être statique ; c’est-à-dire qu’un chemin correspond à une unique route.
Ces chemins sont généralement associés à des pages classiques d’un site web (accueil, contact, qui sommes nous, mentions légales…).
- Le chemin
/
correspond à https://example.com - Le chemin
/contact
à https://example.com/contact - Etc.
app.get('/hello', (req, res) => {
res.send('Hello World!');
})
Chemin dynamique
Le chemin peut aussi être dynamique ; c’est-à-dire que plusieurs chemins peuvent correspondre à une unique route.
On définit un chemin dynamique en faisant usage d’un masque d’URL (ou “URL pattern” en anglais).
Un masque d’URL est une chaîne de caractères utilisée pour définir quelle portion de la route est changeante.
Les masques d’URL peuvent inclure des paramètres dynamiques ou un caractère joker.
Paramètres dynamiques
Les chemins contenant des paramètres dynamiques sont généralement associés à des ressources accessibles sur un site web (articles, produits, utilisateurs…). Ces dernières partagent une même arborescence, une même structure mais des contenus différents.
Un paramètre dynamique est représenté par un nom caractérisant l’information d’URL changeante précédé de :
.
app.get('/users/:id', function(req, res) {
// ...
});
Par exemple, la route /users/:id
utilise un masque d’URL avec un paramètre nommé id
. Cette route peut correspondre à des URLs telles que /users/64
, /users/123
, etc.
Il est également possible de définir des paramètres dynamiques plus spécifiques en exploitant le potentiel des expressions régulières.
Une expression régulière est à définir entre parenthèses, à la fin du paramètre dynamique.
app.get('/users/:id(\\d+)', (req, res) => {
// ...
});
L’expression régulière (\\d+)
correspond à un ou plusieurs chiffres. Cela signifie que ce masque d’URL ne correspondra qu’à des URLs où l’id
est un nombre.
- ✅
/users/123
matchera - ❌
/users/abc
ne matchera pas
Caractère joker *
Le caractère joker *
(ou “wildcard” en anglais) peut être utilisé pour matcher n’importe quelle chaîne de caractères dans une URL.
app.all('/api/*', (req, res) => {
// ...
});
Ici, le masque d’URL /api/*
va matcher toutes les URLs qui commencent par /api/
.
Le caractère *
s’avère très utile pour créer des routes qui correspondent à un ensemble d’URLs partageant une structure commune.
Par exemple, dans une API REST, il est courant d’utiliser des masques d’URL pour les routes assurant les opération CRUD sur des ressources. Le masque /api/*
peut ainsi être utilisé pour matcher toutes ces routes en une seule instruction.
Exemples : /api/users
, /api/users/:id
, /api/articles
…
Handler
Le handler est aussi appelé “fonction de middleware”. Les fonctions de middleware sont des fonctions qui peuvent accéder à l’objet Request
(req
), l’objet Response
(res
) et à la fonction middleware suivante (next()
) dans le cycle demande-réponse de l’application.
Requête
Certaines méthodes de l’objet req
sont très utiles dans le cas du routage, comme :
req.params
: qui permet de récupérer les paramètres de route.
// GET /user/27
app.get('/user/:id', function(req, res) {
console.log(req.params.id); // => '27'
});
req.query
: qui permet de récupérer les paramètres de requête GET
.
// GET /search?keyword=climbing
app.get('/search', function(req, res) {
console.log(req.query.keyword); // => 'climbing'
});
req.body
: qui permet de récupérer les paramètres du corps de la requête.
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.post('/path', function (req, res) {
res.json(req.body);
})
Pour analyser les informations du corps de la requête, on utilise les middlewares intégrés express.json()
et express.urlencoded()
.
express.json()
: permet de parser les données du corps de la requête du format JSON vers un objet JS.express.urlencoded()
: est important pour analyser les données qui sont envoyées depuis un formulaire HTML.
Réponse
Les méthodes de retour de l’objet de réponse res
utilisées par notre gestionnaire de routage sont définies dans le tableau ci-dessous. Si aucune de ces méthodes n’est appelée par le gestionnaire de routage, la demande du client restera bloquée.
Méthode | Description |
---|---|
download() | Permet de télécharger un fichier. |
end() | Termine le processus de réponse. |
json() | Envoie une réponse JSON. |
jsonp() | Envoie une réponse JSON avec une prise en charge JSONP. |
redirect() | Redirige une demande vers une autre route. |
render() | Retourne une vue (.html , .pug …). |
send() | Envoie une réponse de divers types. |
sendFile() | Envoie une réponse sous forme de flux d’octets. |
sendStatus() | Définit le code de statut de réponse et envoie sa représentation sous forme de chaîne comme corps de réponse. |
Les méthodes de réponse principales
Parmi ces méthodes, les plus utilisées sont : json()
dans le cadre du développement d’API et render()
/ redirect()
dans le cadre d’un site web.
Retourner une vue avec render()
app.get('/users/:id', function(req, res) {
// Imaginons que je récupère ici dans la variable 'user' les informations de l'utilisateur ayant pour id req.params.id
res.render('/profile', { user: user });
});
/profile
: c’est le chemin d’accès vers le template de la vue à rendre. Il peut s’agir d’un chemin absolu ou d’un chemin relatif. Le chemin complet et l’extension du fichier ne sont pas spécifiées ici car ces informations sont définies par le moteur de template utilisé par l’application.{ user: user }
: cela représente des données, sous forme d’objet littéral JS, à transmettre au template.
Redirige une demande vers une autre route avec redirect()
app.post('/users', function(req, res) {
res.redirect('/');
});
/
: c’est le chemin vers lequel rediriger l’utilisateur.
Retourner un fichier JSON avec json()
app.get('/users', function(req, res) {
res.json(
[
{ "firstname": 'Camille', "lastname": 'Dupont', "age": '23' },
{ "firstname": 'Laurent', "lastname": 'Dumat', "age": '47' },
{ "firstname": 'Léna', "lastname": 'Moulin', "age": '36' }
]
)
});
Argument de rappel à la fonction middleware
Il est possible d’ajouter un 3ème argument next
à la fonction middleware.
Cet argument, nous permet de bénéficier d’une fonction next()
exécutant la fonction middleware suivante dans le cycle demande-réponse de l’application.
app.get('/users', function(req, res, next) {
// ...
next();
});
Nous détaillerons davantage cette notion de middleware dans le chapitre suivant.
Routeur Express
La classe Router
d’Express permet de créer des gestionnaires de route modulaires et pouvant être montés. Une instance de Router
est un middleware et un système de routage complet.
const express = require('express');
const router = express.Router();
On pourrait donc imaginer avoir un site qui gère :
- Des pages statiques (
home
,about
etcontact
) - Un CRUD d’articles de blog
Il serait alors intéressant de créer un dossier 📁 routes
et d’y ajouter 2 fichiers de routage distincts : 📄 global.js
et 📄 blog.js
.
Routeur exporté dans un module
Exploitons l’objet router
d’Express pour y définir des routes au sein de 2 modules distincts, puis exportons les.
const express = require('express');
const router = express.Router();
router.get('/', function(req, res) {
res.send('Home page');
});
router.get('/about', function(req, res) {
res.send('About page');
});
router.get('/contact', function(req, res) {
res.send('Contact page');
});
module.exports = router;
const express = require('express');
const router = express.Router();
router.get('/', function(req, res) {
res.send('List articles');
});
router.post('/', function(req, res) {
res.send('Create article');
});
router.get('/:id', function(req, res) {
res.send('Read article');
});
module.exports = router;
Chaîner les méthodes HTTP sur une route
Pour éviter de dupliquer des noms de routes et les éventuelles fautes de frappe, il est possible de chaîner des méthodes HTTP différentes sur une même route avec la méthode route()
.
const express = require('express');
const router = express.Router();
router.route('/')
.get(function(req, res) {
res.send('List articles');
})
.post(function(req, res) {
res.send('Create article');
});
router.get('/:id', function(req, res) {
res.send('Read article');
});
module.exports = router;
Chargement des routes
Désormais, il suffit de charger ces deux modules de routage et de les monter sur un chemin dans l’application principale :
const express = require('express');
const app = express();
const globalRouter = require('./routes/global.js');
const blogRouter = require('./routes/blog.js');
app.use('/', globalRouter);
app.use('/blog', blogRouter);
const hostname = '127.0.0.1';
const port = process.env.PORT || 3000;
app.listen(port, hostname, () => {
console.log(`Serveur démarré sur http://${hostname}:${port}`);
});
L’application pourra maintenant gérer les demandes d’accès aux routes suivantes :
/
= home/about
= about/contact
= contact/blog
= list articles / add article/blog/:id
= read article