AzDev

II - Routage avec ExpressJS

ExpressJS ouvre un monde de possibilités pour le développement web avec Node.js. Sa simplicité apparente cache une grande puissance et flexibilité qui vous permettront de créer des applications web modernes, performantes et maintenables. Que vous souhaitiez développer des APIs, des sites web ou des applications temps réel, les compétences que vous acquerrez dans ce cours vous seront précieuses.

Publié le
II - Routage avec ExpressJS

1. Concepts fondamentaux du routage

Méthodes HTTP

Le routage dans ExpressJS fait référence à la façon dont une application répond à une requête client pour un endpoint spécifique, qui est une URI (ou un chemin) et une méthode HTTP spécifique.

Les principales méthodes HTTP prises en charge par Express sont :

  • GET : Récupérer des données
  • POST : Créer de nouvelles données
  • PUT : Mettre à jour des données existantes (remplacement complet)
  • PATCH : Mettre à jour partiellement des données existantes
  • DELETE : Supprimer des données
  • OPTIONS : Obtenir les options de communication pour une ressource
  • HEAD : Similaire à GET mais ne retourne que les en-têtes

La syntaxe de base pour définir une route dans Express est :

1app.METHOD(PATH, HANDLER);

Où :

  • app est une instance d'express
  • METHOD est une méthode HTTP en minuscules
  • PATH est le chemin sur le serveur
  • HANDLER est la fonction exécutée lorsque la route est mise en correspondance

Création de routes simples

Voici des exemples de routes simples pour différentes méthodes HTTP :

1const express = require('express');
2const app = express();
3
4// Route GET pour la page d'accueil
5app.get('/', (req, res) => {
6  res.send('Page d\'accueil');
7});
8
9// Route POST pour soumettre des données
10app.post('/submit', (req, res) => {
11  res.send('Données reçues');
12});
13
14// Route PUT pour mettre à jour une ressource
15app.put('/users/:id', (req, res) => {
16  res.send(`Mise à jour de l'utilisateur ${req.params.id}`);
17});
18
19// Route DELETE pour supprimer une ressource
20app.delete('/users/:id', (req, res) => {
21  res.send(`Suppression de l'utilisateur ${req.params.id}`);
22});
23
24app.listen(3000, () => {
25  console.log('Serveur démarré sur le port 3000');
26});

Paramètres de route

Les paramètres de route sont des segments d'URL nommés qui sont utilisés pour capturer les valeurs spécifiées à leur position dans l'URL. Les valeurs capturées sont stockées dans l'objet req.params.

1// Route avec un paramètre
2app.get('/users/:userId', (req, res) => {
3  res.send(`Profil de l'utilisateur: ${req.params.userId}`);
4});
5
6// Route avec plusieurs paramètres
7app.get('/users/:userId/posts/:postId', (req, res) => {
8  const { userId, postId } = req.params;
9  res.send(`Post ${postId} de l'utilisateur ${userId}`);
10});

Les paramètres de route peuvent être utilisés pour créer des API RESTful, où l'identifiant de la ressource est inclus dans l'URL.

Query strings

Les query strings (chaînes de requête) sont une partie de l'URL qui suit un point d'interrogation (?) et contient des paires clé-valeur séparées par des esperluettes (&). Express analyse automatiquement ces chaînes et les rend disponibles via l'objet req.query.

1// Route avec query string
2// Exemple: /search?q=express&limit=10
3app.get('/search', (req, res) => {
4  const { q, limit } = req.query;
5  res.send(`Recherche de: ${q}, limite: ${limit || 'non spécifiée'}`);
6});

Les query strings sont particulièrement utiles pour :

  • Filtrer des résultats
  • Paginer des données
  • Trier des résultats
  • Spécifier des options de recherche

2. Routes avancées

Routes avec expressions régulières

Express permet d'utiliser des expressions régulières dans les chemins de route pour une correspondance plus flexible :

1// Route qui correspond à tout chemin contenant "api"
2app.get(/.*api.*/, (req, res) => {
3  res.send('Cette route contient "api" dans son chemin');
4});
5
6// Route qui correspond aux fichiers avec une extension .json
7app.get(/^\/data\/.*\.json$/, (req, res) => {
8  res.send('Vous avez demandé un fichier JSON dans le répertoire data');
9});

Attention : Dans Express 5, la gestion des caractères spéciaux comme ?, +, *, et () a changé par rapport à Express 4.

Routes paramétrées

Les routes paramétrées peuvent être rendues plus flexibles avec des modèles de correspondance :

1// Paramètre optionnel avec ?
2// Correspond à /user et /user/42
3app.get('/user/:id?', (req, res) => {
4  if (req.params.id) {
5    res.send(`Utilisateur avec ID: ${req.params.id}`);
6  } else {
7    res.send('Liste des utilisateurs');
8  }
9});
10
11// Paramètre avec contrainte de format (Express 4.x)
12app.get('/user/:id(\\d+)', (req, res) => {
13  // Ne correspond qu'aux IDs numériques
14  res.send(`Utilisateur avec ID numérique: ${req.params.id}`);
15});

Gestion des routes avec des callbacks multiples

Express permet d'utiliser plusieurs fonctions de callback pour une même route, ce qui est utile pour diviser la logique en étapes :

1// Middleware de vérification d'authentification
2function checkAuth(req, res, next) {
3  if (req.query.token === 'secret') {
4    next(); // Passe au middleware suivant
5  } else {
6    res.status(401).send('Non autorisé');
7  }
8}
9
10// Middleware de vérification des permissions
11function checkPermission(req, res, next) {
12  if (req.params.id === '1' || req.params.id === '2') {
13    next();
14  } else {
15    res.status(403).send('Accès interdit');
16  }
17}
18
19// Route avec plusieurs middlewares
20app.get('/admin/:id', checkAuth, checkPermission, (req, res) => {
21  res.send(`Bienvenue admin ${req.params.id}`);
22});

Chaînage de routes

Express permet de chaîner les définitions de routes pour le même chemin mais avec différentes méthodes HTTP :

1app.route('/book')
2  .get((req, res) => {
3    res.send('Obtenir tous les livres');
4  })
5  .post((req, res) => {
6    res.send('Ajouter un nouveau livre');
7  })
8  .put((req, res) => {
9    res.send('Mettre à jour tous les livres');
10  });
11
12app.route('/book/:id')
13  .get((req, res) => {
14    res.send(`Obtenir le livre ${req.params.id}`);
15  })
16  .put((req, res) => {
17    res.send(`Mettre à jour le livre ${req.params.id}`);
18  })
19  .delete((req, res) => {
20    res.send(`Supprimer le livre ${req.params.id}`);
21  });

Cette approche réduit la duplication de code et améliore la lisibilité.

3. Organisation des routes

Création de routeurs modulaires

Pour les applications plus grandes, il est recommandé d'organiser les routes en modules séparés à l'aide de express.Router(). Cela permet de :

  • Créer des routeurs modulaires et montables
  • Encapsuler les routes liées à une ressource spécifique
  • Améliorer la maintenabilité du code

Exemple d'organisation :

1// routes/users.js
2const express = require('express');
3const router = express.Router();
4
5// Middleware spécifique à ce routeur
6router.use((req, res, next) => {
7  console.log('Time:', Date.now());
8  next();
9});
10
11// Routes pour les utilisateurs
12router.get('/', (req, res) => {
13  res.send('Liste des utilisateurs');
14});
15
16router.get('/:id', (req, res) => {
17  res.send(`Détails de l'utilisateur ${req.params.id}`);
18});
19
20router.post('/', (req, res) => {
21  res.send('Création d\'un nouvel utilisateur');
22});
23
24module.exports = router;
1// routes/products.js
2const express = require('express');
3const router = express.Router();
4
5router.get('/', (req, res) => {
6  res.send('Liste des produits');
7});
8
9router.get('/:id', (req, res) => {
10  res.send(`Détails du produit ${req.params.id}`);
11});
12
13module.exports = router;
1// app.js
2const express = require('express');
3const app = express();
4
5// Importation des routeurs
6const usersRouter = require('./routes/users');
7const productsRouter = require('./routes/products');
8
9// Montage des routeurs sur des chemins spécifiques
10app.use('/users', usersRouter);
11app.use('/products', productsRouter);
12
13app.listen(3000, () => {
14  console.log('Serveur démarré sur le port 3000');
15});

Utilisation de express.Router()

Le routeur Express est un objet de routage complet qui fonctionne comme une mini-application capable de réaliser des opérations de middleware et de routage.

Caractéristiques importantes :

  • Chaque routeur est une instance de middleware et de routage
  • Les routeurs peuvent être imbriqués
  • Les routeurs peuvent avoir leurs propres middlewares
  • Les paramètres de route sont préservés lors du montage
1// Exemple de routeur avec paramètre
2const router = express.Router();
3
4// Middleware pour ce routeur uniquement
5router.use('/:id', (req, res, next) => {
6  console.log(`ID demandé: ${req.params.id}`);
7  next();
8});
9
10// Gestion des paramètres
11router.param('id', (req, res, next, id) => {
12  // Validation ou traitement du paramètre
13  if (isNaN(id)) {
14    return res.status(400).send('ID invalide');
15  }
16  req.userId = parseInt(id, 10);
17  next();
18});
19
20router.get('/:id', (req, res) => {
21  res.send(`Utilisateur avec ID: ${req.userId}`);
22});
23
24// Montage du routeur
25app.use('/users', router);
26// Accessible via /users/123

Bonnes pratiques d'organisation

Pour maintenir une application Express organisée et évolutive, suivez ces bonnes pratiques :

  1. Structure par domaine : Organisez les routes par domaine fonctionnel (utilisateurs, produits, etc.)

  2. Séparation des préoccupations : Séparez les routes, les contrôleurs et les modèles

routes/
  ├── index.js       # Exporte tous les routeurs
  ├── users.js       # Routes liées aux utilisateurs
  └── products.js    # Routes liées aux produits
controllers/
  ├── userController.js
  └── productController.js
models/
  ├── User.js
  └── Product.js
  1. Contrôleurs dédiés : Déplacez la logique des gestionnaires de route dans des contrôleurs dédiés
1// controllers/userController.js
2exports.getAllUsers = (req, res) => {
3  // Logique pour récupérer tous les utilisateurs
4  res.send('Liste des utilisateurs');
5};
6
7exports.getUserById = (req, res) => {
8  // Logique pour récupérer un utilisateur par ID
9  res.send(`Utilisateur ${req.params.id}`);
10};
11
12// routes/users.js
13const express = require('express');
14const router = express.Router();
15const userController = require('../controllers/userController');
16
17router.get('/', userController.getAllUsers);
18router.get('/:id', userController.getUserById);
19
20module.exports = router;
  1. Middlewares spécifiques aux routes : Utilisez des middlewares spécifiques pour chaque groupe de routes

  2. Gestion centralisée des erreurs : Utilisez un middleware d'erreur global

Versionnement d'API

Pour les API destinées à évoluer dans le temps, le versionnement est essentiel :

  1. Versionnement par URL :
1// v1 API
2const v1Router = express.Router();
3app.use('/api/v1', v1Router);
4
5v1Router.get('/users', (req, res) => {
6  // Implémentation v1
7});
8
9// v2 API
10const v2Router = express.Router();
11app.use('/api/v2', v2Router);
12
13v2Router.get('/users', (req, res) => {
14  // Implémentation v2 améliorée
15});
  1. Versionnement par en-tête :
1app.get('/api/users', (req, res) => {
2  const apiVersion = req.headers['accept-version'] || '1.0';
3  
4  if (apiVersion === '1.0') {
5    // Implémentation v1
6  } else if (apiVersion === '2.0') {
7    // Implémentation v2
8  } else {
9    res.status(400).send('Version API non supportée');
10  }
11});
  1. Versionnement par paramètre de requête :
1app.get('/api/users', (req, res) => {
2  const apiVersion = req.query.version || '1';
3  
4  if (apiVersion === '1') {
5    // Implémentation v1
6  } else if (apiVersion === '2') {
7    // Implémentation v2
8  } else {
9    res.status(400).send('Version API non supportée');
10  }
11});

Le versionnement par URL est généralement préféré pour sa simplicité et sa visibilité.

Exercices pratiques

Exercice 1: Créer un ensemble de routes pour une API de gestion de tâches

Créez une API RESTful pour gérer une liste de tâches avec les routes suivantes :

  • GET /tasks - Récupérer toutes les tâches
  • GET /tasks/:id - Récupérer une tâche spécifique
  • POST /tasks - Créer une nouvelle tâche
  • PUT /tasks/:id - Mettre à jour une tâche existante
  • DELETE /tasks/:id - Supprimer une tâche

Exercice 2: Implémenter des routes paramétrées

Créez une API pour un blog avec des routes paramétrées :

  • GET /posts/:year/:month? - Récupérer les articles d'une année et optionnellement d'un mois spécifique
  • GET /categories/:categoryName/posts - Récupérer les articles d'une catégorie spécifique

Exercice 3: Organiser une application avec des routeurs modulaires

Réorganisez une application Express existante en utilisant des routeurs modulaires pour différentes ressources (utilisateurs, produits, commandes, etc.).

Ressources supplémentaires