AzDev

IV - Gestion des requêtes et réponses

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
IV - Gestion des requêtes et réponses

1. Objets Request et Response

Propriétés et méthodes de l'objet Request

L'objet Request (req) dans Express représente la requête HTTP et possède de nombreuses propriétés et méthodes utiles pour accéder aux données envoyées par le client.

Propriétés principales

  • req.params : Contient les paramètres de route (parties de l'URL marquées par :)

    1// Route: /users/:userId
    2// URL: /users/123
    3console.log(req.params.userId); // "123"
  • req.query : Contient les paramètres de requête (après le ? dans l'URL)

    1// URL: /search?q=express&limit=10
    2console.log(req.query.q);     // "express"
    3console.log(req.query.limit); // "10"
  • req.body : Contient les données soumises dans le corps de la requête (nécessite un middleware comme express.json() ou express.urlencoded())

    1// POST avec JSON: { "name": "John", "age": 30 }
    2console.log(req.body.name); // "John"
    3console.log(req.body.age);  // 30
  • req.headers : Contient les en-têtes HTTP de la requête

    1console.log(req.headers['user-agent']); // Informations sur le navigateur
    2console.log(req.headers.authorization);  // En-tête d'autorisation
  • req.cookies : Contient les cookies envoyés par le client (nécessite le middleware cookie-parser)

    1console.log(req.cookies.sessionId); // Valeur du cookie "sessionId"
  • req.ip : Adresse IP du client

    1console.log(req.ip); // "192.168.1.1"
  • req.path : Chemin de la requête

    1// URL: /users/profile?sort=desc
    2console.log(req.path); // "/users/profile"
  • req.protocol : Protocole de la requête ('http' ou 'https')

    1console.log(req.protocol); // "http" ou "https"
  • req.secure : Boolean indiquant si la connexion est sécurisée (HTTPS)

    1console.log(req.secure); // true pour HTTPS, false pour HTTP
  • req.method : Méthode HTTP de la requête

    1console.log(req.method); // "GET", "POST", etc.

Méthodes utiles

  • req.get() : Récupère un en-tête HTTP spécifique

    1console.log(req.get('Content-Type')); // "application/json"
  • req.is() : Vérifie le type de contenu de la requête

    1if (req.is('json')) {
    2  // Traitement pour JSON
    3}
  • req.accepts() : Vérifie si le client accepte un type de contenu spécifique

    1if (req.accepts('html')) {
    2  res.send('<h1>Hello</h1>');
    3} else if (req.accepts('json')) {
    4  res.json({ hello: 'world' });
    5}

Propriétés et méthodes de l'objet Response

L'objet Response (res) représente la réponse HTTP qu'Express envoie au client.

Méthodes principales

  • res.send() : Envoie une réponse de n'importe quel type

    1res.send('Hello World');
    2res.send({ name: 'John' });
    3res.send(Buffer.from('binary data'));
  • res.json() : Envoie une réponse JSON

    1res.json({ user: 'John', id: 123 });
  • res.status() : Définit le code de statut HTTP

    1res.status(404).send('Page non trouvée');
  • res.sendFile() : Envoie un fichier

    1res.sendFile('/path/to/file.pdf');
  • res.render() : Rend un template avec des données

    1res.render('profile', { user: 'John' });
  • res.redirect() : Redirige vers une autre URL

    1res.redirect('/login');
    2res.redirect(301, 'https://example.com');
  • res.set() : Définit un en-tête HTTP

    1res.set('Content-Type', 'text/plain');
    2res.set({
    3  'Content-Type': 'text/plain',
    4  'X-Custom-Header': 'Custom Value'
    5});
  • res.cookie() : Définit un cookie

    1res.cookie('name', 'value', { maxAge: 900000, httpOnly: true });
  • res.clearCookie() : Supprime un cookie

    1res.clearCookie('name');
  • res.download() : Invite à télécharger un fichier

    1res.download('/path/to/file.pdf', 'rapport.pdf');
  • res.end() : Termine la réponse sans envoyer de données

    1res.status(200).end();
  • res.format() : Répond différemment selon l'en-tête Accept

    1res.format({
    2  'text/plain': () => {
    3    res.send('Bonjour');
    4  },
    5  'text/html': () => {
    6    res.send('<h1>Bonjour</h1>');
    7  },
    8  'application/json': () => {
    9    res.json({ message: 'Bonjour' });
    10  },
    11  default: () => {
    12    res.status(406).send('Not Acceptable');
    13  }
    14});

Cycle de vie d'une requête-réponse

Le cycle de vie d'une requête-réponse dans Express suit ces étapes :

  1. Réception de la requête : Express reçoit la requête HTTP du client
  2. Traitement par les middlewares : La requête traverse les middlewares configurés
  3. Correspondance de route : Express trouve la route correspondante
  4. Exécution du gestionnaire de route : Le callback de la route est exécuté
  5. Génération de la réponse : Le gestionnaire génère une réponse
  6. Envoi de la réponse : La réponse est envoyée au client

Ce cycle peut être interrompu à tout moment si un middleware ou un gestionnaire de route :

  • Envoie une réponse (avec res.send(), res.json(), etc.)
  • Redirige la requête (avec res.redirect())
  • Passe une erreur à next()

2. Traitement des données entrantes

Paramètres d'URL

Les paramètres d'URL (ou paramètres de route) sont des segments variables dans l'URL, définis avec un préfixe : dans la définition de la route.

1app.get('/users/:userId/posts/:postId', (req, res) => {
2  const { userId, postId } = req.params;
3  res.send(`Affichage du post ${postId} de l'utilisateur ${userId}`);
4});

Vous pouvez définir des contraintes sur les paramètres avec des expressions régulières :

1// Dans Express 4.x
2app.get('/users/:userId(\\d+)', (req, res) => {
3  // Ne correspond qu'aux IDs numériques
4  res.send(`Utilisateur ${req.params.userId}`);
5});

Query strings

Les query strings sont des paires clé-valeur ajoutées à la fin de l'URL après un point d'interrogation (?).

1// URL: /search?q=express&sort=name&page=2
2app.get('/search', (req, res) => {
3  const { q, sort, page } = req.query;
4  
5  // Valeurs par défaut et conversion de types
6  const searchTerm = q || '';
7  const sortBy = sort || 'relevance';
8  const pageNum = parseInt(page, 10) || 1;
9  
10  res.send(`Recherche de "${searchTerm}", tri par ${sortBy}, page ${pageNum}`);
11});

Les query strings sont particulièrement utiles pour :

  • Filtrage : ?category=books
  • Tri : ?sort=price&order=desc
  • Pagination : ?page=2&limit=10
  • Recherche : ?q=express

Corps de requête (JSON, formulaires)

Pour traiter les données envoyées dans le corps de la requête, vous devez utiliser des middlewares appropriés :

1const express = require('express');
2const app = express();
3
4// Pour les données JSON
5app.use(express.json());
6
7// Pour les données de formulaire
8app.use(express.urlencoded({ extended: true }));
9
10// Traitement d'une requête POST avec JSON
11app.post('/api/users', (req, res) => {
12  const { name, email, age } = req.body;
13  
14  // Validation des données
15  if (!name || !email) {
16    return res.status(400).json({ error: 'Nom et email requis' });
17  }
18  
19  // Traitement des données...
20  
21  res.status(201).json({ 
22    message: 'Utilisateur créé', 
23    user: { name, email, age } 
24  });
25});
26
27// Traitement d'un formulaire
28app.post('/login', (req, res) => {
29  const { username, password } = req.body;
30  
31  // Authentification...
32  
33  res.redirect('/dashboard');
34});

Pour des formulaires avec upload de fichiers, vous aurez besoin d'un middleware supplémentaire comme multer :

1const multer = require('multer');
2const upload = multer({ dest: 'uploads/' });
3
4app.post('/profile', upload.single('avatar'), (req, res) => {
5  // req.file contient les informations sur le fichier uploadé
6  // req.body contient les autres champs du formulaire
7  res.send('Profil mis à jour');
8});

Upload de fichiers

L'upload de fichiers nécessite généralement le middleware multer :

1const express = require('express');
2const multer = require('multer');
3const path = require('path');
4const app = express();
5
6// Configuration de stockage
7const storage = multer.diskStorage({
8  destination: (req, file, cb) => {
9    cb(null, 'uploads/');  // Dossier de destination
10  },
11  filename: (req, file, cb) => {
12    // Génération d'un nom de fichier unique
13    const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
14    cb(null, file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname));
15  }
16});
17
18// Filtrage des fichiers
19const fileFilter = (req, file, cb) => {
20  // Accepter uniquement certains types de fichiers
21  if (file.mimetype.startsWith('image/')) {
22    cb(null, true);
23  } else {
24    cb(new Error('Seules les images sont autorisées'), false);
25  }
26};
27
28// Initialisation de multer
29const upload = multer({ 
30  storage: storage,
31  limits: {
32    fileSize: 5 * 1024 * 1024  // Limite à 5MB
33  },
34  fileFilter: fileFilter
35});
36
37// Upload d'un seul fichier
38app.post('/upload/profile', upload.single('avatar'), (req, res) => {
39  if (!req.file) {
40    return res.status(400).send('Aucun fichier uploadé');
41  }
42  
43  res.json({
44    message: 'Fichier uploadé avec succès',
45    file: {
46      name: req.file.filename,
47      size: req.file.size,
48      mimetype: req.file.mimetype
49    }
50  });
51});
52
53// Upload de plusieurs fichiers
54app.post('/upload/gallery', upload.array('photos', 12), (req, res) => {
55  if (!req.files || req.files.length === 0) {
56    return res.status(400).send('Aucun fichier uploadé');
57  }
58  
59  res.json({
60    message: `${req.files.length} fichiers uploadés avec succès`,
61    files: req.files.map(file => ({
62      name: file.filename,
63      size: file.size,
64      mimetype: file.mimetype
65    }))
66  });
67});
68
69// Gestion des erreurs d'upload
70app.use((err, req, res, next) => {
71  if (err instanceof multer.MulterError) {
72    // Erreur Multer
73    return res.status(400).json({
74      error: true,
75      message: err.message
76    });
77  } else if (err) {
78    // Autre erreur
79    return res.status(500).json({
80      error: true,
81      message: err.message
82    });
83  }
84  next();
85});

3. Formatage des réponses

Envoi de texte et HTML

Pour envoyer du texte brut ou du HTML, utilisez res.send() :

1// Texte brut
2app.get('/text', (req, res) => {
3  res.set('Content-Type', 'text/plain');
4  res.send('Ceci est du texte brut');
5});
6
7// HTML
8app.get('/html', (req, res) => {
9  res.send(`
10    <!DOCTYPE html>
11    <html>
12      <head>
13        <title>Page Express</title>
14      </head>
15      <body>
16        <h1>Bienvenue sur Express</h1>
17        <p>Ceci est une page HTML générée par Express</p>
18      </body>
19    </html>
20  `);
21});

Envoi de JSON

Pour envoyer des données JSON, utilisez res.json() :

1app.get('/api/users', (req, res) => {
2  const users = [
3    { id: 1, name: 'Alice', email: 'alice@example.com' },
4    { id: 2, name: 'Bob', email: 'bob@example.com' }
5  ];
6  
7  res.json(users);
8});
9
10app.get('/api/users/:id', (req, res) => {
11  const userId = parseInt(req.params.id, 10);
12  
13  // Simuler une recherche en base de données
14  const user = { id: userId, name: 'Alice', email: 'alice@example.com' };
15  
16  if (user) {
17    res.json(user);
18  } else {
19    res.status(404).json({ error: 'Utilisateur non trouvé' });
20  }
21});

res.json() définit automatiquement le Content-Type à application/json et convertit les objets JavaScript en JSON.

Envoi de fichiers

Pour envoyer des fichiers, utilisez res.sendFile() :

1const path = require('path');
2
3app.get('/document', (req, res) => {
4  const filePath = path.join(__dirname, 'documents', 'rapport.pdf');
5  
6  res.sendFile(filePath, (err) => {
7    if (err) {
8      console.error('Erreur lors de l\'envoi du fichier:', err);
9      res.status(500).send('Erreur lors de l\'envoi du fichier');
10    }
11  });
12});

Pour forcer le téléchargement d'un fichier, utilisez res.download() :

1app.get('/download/rapport', (req, res) => {
2  const filePath = path.join(__dirname, 'documents', 'rapport.pdf');
3  
4  res.download(filePath, 'rapport-annuel.pdf', (err) => {
5    if (err) {
6      console.error('Erreur lors du téléchargement:', err);
7      if (!res.headersSent) {
8        res.status(500).send('Erreur lors du téléchargement');
9      }
10    }
11  });
12});

Redirection

Pour rediriger vers une autre URL, utilisez res.redirect() :

1// Redirection 302 (temporaire) par défaut
2app.get('/old-page', (req, res) => {
3  res.redirect('/new-page');
4});
5
6// Redirection 301 (permanente)
7app.get('/permanent-redirect', (req, res) => {
8  res.redirect(301, '/new-location');
9});
10
11// Redirection relative
12app.get('/products', (req, res) => {
13  res.redirect('categories');  // Redirige vers /products/categories
14});
15
16// Redirection en arrière
17app.get('/cancel', (req, res) => {
18  res.redirect('back');  // Redirige vers la page précédente (Referer)
19});

Statuts HTTP

Il est important d'utiliser les codes de statut HTTP appropriés pour vos réponses :

1// 200 OK (par défaut pour res.send())
2app.get('/success', (req, res) => {
3  res.status(200).send('Succès');
4});
5
6// 201 Created
7app.post('/api/resources', (req, res) => {
8  // Création d'une ressource...
9  res.status(201).json({ id: 123, message: 'Ressource créée' });
10});
11
12// 204 No Content
13app.delete('/api/resources/:id', (req, res) => {
14  // Suppression d'une ressource...
15  res.status(204).end();
16});
17
18// 400 Bad Request
19app.post('/api/validate', (req, res) => {
20  if (!req.body.email) {
21    return res.status(400).json({ error: 'Email requis' });
22  }
23  res.json({ valid: true });
24});
25
26// 401 Unauthorized
27app.get('/api/protected', (req, res) => {
28  if (!req.headers.authorization) {
29    return res.status(401).json({ error: 'Authentification requise' });
30  }
31  res.json({ data: 'Contenu protégé' });
32});
33
34// 403 Forbidden
35app.get('/api/admin', (req, res) => {
36  if (req.user && req.user.role !== 'admin') {
37    return res.status(403).json({ error: 'Accès interdit' });
38  }
39  res.json({ data: 'Contenu administrateur' });
40});
41
42// 404 Not Found
43app.get('/api/resources/:id', (req, res) => {
44  const resource = findResourceById(req.params.id);
45  if (!resource) {
46    return res.status(404).json({ error: 'Ressource non trouvée' });
47  }
48  res.json(resource);
49});
50
51// 500 Internal Server Error
52app.get('/api/error', (req, res) => {
53  try {
54    // Opération qui peut échouer
55    throw new Error('Erreur interne');
56  } catch (err) {
57    console.error(err);
58    res.status(500).json({ error: 'Erreur serveur' });
59  }
60});

Exercices pratiques

Exercice 1 : Créer un formulaire et traiter sa soumission avec ExpressJS

Créez une application Express qui affiche un formulaire d'inscription et traite sa soumission. Le formulaire doit inclure des champs pour le nom, l'email et le mot de passe. Validez les données soumises et affichez un message de confirmation.

Exercice 2 : Implémenter un système d'upload de fichiers

Créez une application qui permet aux utilisateurs d'uploader des images. Limitez les types de fichiers acceptés aux images (JPEG, PNG, GIF) et la taille maximale à 5MB. Affichez les images uploadées dans une galerie.

Exercice 3 : Créer une API qui répond en différents formats

Créez une API qui peut renvoyer des données dans différents formats (JSON, XML, HTML) en fonction de l'en-tête Accept envoyé par le client. Utilisez res.format() pour gérer les différents formats.

Ressources supplémentaires