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.

Icône de calendrier
Intermédiaire
8 chapitres

Structure d’une route

Dans Express, une route est définie selon la structure suivante :

copié !
app.method(path, handler);
  • app est une instance d’Express
  • method est une méthode HTTP
  • path est le chemin demandé par le client
  • handler 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.

copié !
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.

copié !
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.

copié !
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.

copié !
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.

copié !
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).

copié !
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…).

copié !
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 :.

copié !
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.

copié !
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.

copié !
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.

copié !
// 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.

copié !
// 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.

copié !
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éthodeDescription
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()
copié !
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()
copié !
app.post('/users', function(req, res) {
	res.redirect('/');
});

/ : c’est le chemin vers lequel rediriger l’utilisateur.

Retourner un fichier JSON avec json()
copié !
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.

copié !
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.

copié !
const express = require('express');
const router = express.Router();

On pourrait donc imaginer avoir un site qui gère :

  • Des pages statiques (home, about et contact)
  • 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.

routes/global.js
copié !
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;
routes/blog.js
copié !
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().

routes/blog.js
copié !
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 :

app.js
copié !
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