Développement d'applications Express : Générateur et middlewares essentiels

La mise en place d'une application web avec Node.js et le framework Express implique souvent l'utilisation d'outils pour accélérer le développement et de middlewares pour gérer des fonctionnalités courantes. Cet article explore l'utilisation du générateur Express pour initialiser un projet et l'intégration de plusieurs middlewares tiers cruciaux.

1. Initialisation d'un projet Express avec le générateur

Le générateur Express simplifie la création d'une structure de projet de base. Si vous ne l'avez pas déjà fait, installez-le globalement :

npm install -g express-generator

Pour vérifier les options disponibles, exécutez :

express -h

Les étapes suivantes détaillent la création d'une nouvelle application :

  1. Créez un nouveau projet en spécifiant le moteur de template EJS et le nom du dossier :
express --view=ejs monAppExpress

Ceci génère une structure de dossier standard et configure EJS comme moteur de vues.

  1. Naviguez dans le répertoire de votre nouvelle application :
cd monAppExpress

  1. Installez toutes les dépendances listées dans le fichier package.json :
npm install

  1. Pour faciliter le développement avec le rechargement automatique, nous allons configurer nodemon. Commencez par l'installer globalement si ce n'est pas déjà fait :
npm install -g nodemon

  1. Ensuite, ajoutez un script de développement à votre fichier package.json pour utiliser nodemon :
{
  "name": "monAppExpress",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "start": "node ./bin/www",
    "dev": "nodemon ./bin/www"
  },
  "dependencies": {
    // ... vos dépendances ...
  }
}

Vous pourrez désormais démarrer votre application en mode développement avec :

npm run dev

  1. Modifiez le fichier ./bin/www pour inclure un message de confirmation lorsque le serveur démarre. Recherchez la ligne où le serveur écoute le port et ajoutez une fonction de rappel :
server.listen(port, () => {
  console.log(`Serveur Express démarré sur http://localhost:${port}`);
});

2. Intégration de middlewares tiers essentiels

2.1. Redémarrage automatique avec Nodemon

nodemon est un utilitaire qui surveille les changements dans les fichiers de votre projet et redémarre automatiquement l'application Node.js. C'est un gain de temps considérable en phase de développement.

Après l'installation globale mentionnée précédemment (npm install -g nodemon), vous pouvez créer un fichier de configuration nommé nodemon.json à la racine de votre projet pour personnaliser son comportement :

{
  "restartable": "rs",
  "ignore": [
    ".git",
    ".svn",
    "node_modules/**/node_modules"
  ],
  "verbose": true,
  "execMap": {
    "js": "node --harmony"
  },
  "watch": [],
  "env": {
    "NODE_ENV": "development"
  },
  "ext": "js json"
}

Ce fichier indique à nodemon d'ignorer certains répertoires, d'utiliser le mode verbeux, et de surveiller les fichiers JavaScript et JSON. Avec le script dev configuré dans package.json, vous n'avez plus qu'à lancer npm run dev.

2.2. Gestion des données de formulaire avec Formidable

Le middleware formidable est indispesnable pour analyser les données de formulaires HTML, y compris les téléchargements de fichiers. Il transforme les données POST en objets JavaScript accessibles.

Installez formidable dans votre projet :

npm install formidable

Voici comment l'utiliser dans une route Express pour traiter un formulaire d'inscription avec un téléchargement de photo :

// Dans votre fichier app.js ou une route spécifique
const formidable = require('formidable');
const fs = require('fs');
const path = require('path');

// Assurez-vous que le dossier 'uploads' existe
const UPLOAD_DIR = './uploads';
if (!fs.existsSync(UPLOAD_DIR)) {
  fs.mkdirSync(UPLOAD_DIR);
}
// Assurez-vous que le dossier 'data' existe pour sauvegarder les profils
const DATA_DIR = './data';
if (!fs.existsSync(DATA_DIR)) {
  fs.mkdirSync(DATA_DIR);
}

// Route POST pour l'inscription
app.post('/inscription', (req, res) => {
  const formParser = formidable({
    multiples: true, // Gère les champs de fichier multiples
    uploadDir: UPLOAD_DIR, // Répertoire temporaire pour les fichiers uploadés
  });

  formParser.parse(req, (err, champs, fichiers) => {
    if (err) {
      console.error('Erreur lors de l\'analyse du formulaire :', err);
      return res.status(500).send('Erreur lors de l\'analyse du formulaire.');
    }

    // Récupération des champs textuels
    const { nomUtilisateur, ageUtilisateur } = champs;
    // Récupération du fichier téléchargé
    const fichierPhoto = fichiers.photo ? fichiers.photo[0] : null;

    if (fichierPhoto) {
      const cheminTemporaire = fichierPhoto.filepath;
      const nomOriginal = fichierPhoto.originalFilename;
      const extension = path.extname(nomOriginal);
      const nouveauNomFichier = path.basename(cheminTemporaire) + extension;
      const cheminFinalPhoto = path.join(UPLOAD_DIR, nouveauNomFichier);

      // Renomme le fichier temporaire avec son extension d'origine
      fs.rename(cheminTemporaire, cheminFinalPhoto, (renameErr) => {
        if (renameErr) {
          console.error('Erreur lors du renommage du fichier :', renameErr);
          return res.status(500).send('Erreur serveur lors du traitement de la photo.');
        }

        // Crée un objet pour le profil utilisateur
        const profilUtilisateur = {
          nom: nomUtilisateur,
          age: ageUtilisateur,
          photo: cheminFinalPhoto, // Chemin relatif ou absolu du fichier
        };

        // Sauvegarde le profil dans un fichier JSON
        fs.writeFile(path.join(DATA_DIR, `${nomUtilisateur}.json`), JSON.stringify(profilUtilisateur, null, 2), (writeErr) => {
          if (writeErr) {
            console.error('Erreur lors de la sauvegarde du profil :', writeErr);
            return res.status(500).send('Erreur serveur lors de la sauvegarde des données.');
          }
          res.send(`Inscription réussie pour ${nomUtilisateur} !`);
        });
      });
    } else {
      res.status(400).send('Veuillez télécharger une photo.');
    }
  });
});

// Route pour afficher la liste des inscrits
app.get('/liste-inscrits', (req, res) => {
  fs.readdir(DATA_DIR, (err, fichiersProfils) => {
    if (err) {
      console.error('Erreur lors de la lecture des profils :', err);
      return res.status(500).send('Erreur serveur.');
    }

    let htmlTable = `
      <style>table, th, td { border: 1px solid #ccc; border-collapse: collapse; padding: 8px; }</style>
      <table>
        <tr><th>Nom</th><th>Âge</th><th>Photo</th></tr>
    `;

    fichiersProfils.forEach(nomFichier => {
      if (path.extname(nomFichier) === '.json') {
        const contenuFichier = fs.readFileSync(path.join(DATA_DIR, nomFichier), 'utf8');
        const profil = JSON.parse(contenuFichier);
        const cheminPhotoAffichage = profil.photo.replace(UPLOAD_DIR + path.sep, ''); // Supprime le chemin local pour le rendre relatif à /fichiers-media
        htmlTable += `
          <tr>
            <td>${profil.nom}</td>
            <td>${profil.age}</td>
            <td><img src="/fichiers-media/${cheminPhotoAffichage}" width="100" height="60" alt="Photo de ${profil.nom}"></td>
          </tr>
        `;
      }
    });

    htmlTable += `</table>`;
    res.send(htmlTable);
  });
});

// Route pour servir les fichiers media (photos)
app.get('/fichiers-media/:nomFichier', (req, res) => {
  const nomFichier = req.params.nomFichier;
  const cheminFichier = path.join(UPLOAD_DIR, nomFichier);

  fs.readFile(cheminFichier, (err, data) => {
    if (err) {
      console.error('Erreur lors de la lecture du fichier media :', err);
      return res.status(404).send('Fichier non trouvé.');
    }
    res.type(path.extname(cheminFichier)); // Définit le Content-Type approprié
    res.send(data);
  });
});

2.3. Gestion des cookies avec Cookie-Parser

Le middleware cookie-parser permet à Express de parser les cookies envoyés par le client et de les rendre accessibles via req.cookies. Les cookies sont des petits morceaux de données stockés côté client, utiles pour la persistance de session, les préférences utilisateur ou les informations de connexion.

Installez le middleware :

npm install cookie-parser

Configurez-le dans votre application Express avant vos définitions de routes :

const cookieParser = require('cookie-parser');
const express = require('express');
const app = express();

// Configuration du middleware cookie-parser
app.use(cookieParser());

// Route pour définir un cookie
app.get('/definir-cookie', (req, res) => {
  res.cookie('identifiantUtilisateur', 'utilisateur_alpha', { maxAge: 900000, httpOnly: true });
  res.send('Cookie "identifiantUtilisateur" défini.');
});

// Route pour lire un cookie
app.get('/accueil', (req, res) => {
  const identifiant = req.cookies.identifiantUtilisateur;

  if (!identifiant) {
    res.send('Veuillez vous connecter. <a href="/connexion">Aller à la page de connexion</a>');
  } else {
    res.send(`Bienvenue ${identifiant} sur la page d'accueil !`);
  }
});

// Exemple de page de connexion (pour le lien ci-dessus)
app.get('/connexion', (req, res) => {
  res.send(`
    <h1>Page de Connexion</h1>
    <p>Cliquez <a href="/definir-cookie">ici pour vous "connecter" et définir le cookie</a>.</p>
    <p>Ensuite, retournez à la <a href="/accueil">page d'accueil</a>.</p>
  `);
});

// Démarrez le serveur
app.listen(3000, () => {
  console.log('Serveur démarré sur http://localhost:3000');
});

Cookies signés : Pour prévenir la falsification côté client, vous pouvez signer vos cookies. Cela ne les chiffre pas, mais permet au serveur de vérifier leur intégrité.

  1. Lorsque vous définissez le cookie, ajoutez l'option signed: true :
res.cookie('tokenSession', 'valeurSecrete', { maxAge: 1000 * 60 * 60 * 24, signed: true });

  1. Lors de la configuration de cookie-parser, fournissez une chaîne secrète unique :
app.use(cookieParser('maSuperCleSecretePourLesCookies'));

  1. Pour lire un cookie signé, utilisez req.signedCookies :
const token = req.signedCookies.tokenSession;
if (token) {
  console.log('Token de session valide :', token);
} else {
  console.log('Token de session absent ou falsifié.');
}

2.4. Gestion des sessions avec Express-Session

Les sessions sont une technique de stockage de données côté serveur, associées à un utilisateur via un ID de session (généralement stocké dans un cookie). Elles sont utiles pour conserver des informations sensibles ou complexes qui ne devraient pas être exposées au client.

Installez le middleware :

npm install express-session

Configurez express-session dans votre application :

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

const sessionConfig = {
  secret: 'unePhraseSecreteTresLongueEtComplexe', // Clé secrète pour signer l'ID de session
  name: 'monIdSession', // Nom du cookie de session
  resave: false, // Ne pas forcer la sauvegarde de la session si elle n'a pas été modifiée
  saveUninitialized: true, // Sauvegarder les sessions non initialisées (nouvelles sessions sans modification)
  cookie: {
    httpOnly: true, // Le cookie n'est pas accessible via JavaScript côté client
    secure: false, // true en production avec HTTPS
    maxAge: 1000 * 60 * 60 * 24 // Durée de vie du cookie (1 jour)
  }
};

app.use(session(sessionConfig));

// Route pour initialiser ou modifier des données de session
app.get('/definir-session', (req, res) => {
  req.session.utilisateurConnecte = 'Admin';
  req.session.role = 'administrateur';
  req.session.messageBienvenue = `Bienvenue ${req.session.utilisateurConnecte} (${req.session.role}) !`;
  res.send('Données de session définies.');
});

// Route pour lire les données de session
app.get('/verifier-session', (req, res) => {
  if (req.session.utilisateurConnecte) {
    res.send(`Session active pour : ${req.session.utilisateurConnecte}. Rôle : ${req.session.role}. Message : ${req.session.messageBienvenue}`);
  } else {
    res.send('Aucune session active. <a href="/definir-session">Démarrer une session</a>.');
  }
});

// Démarrez le serveur
app.listen(3000, () => {
  console.log('Serveur démarré sur http://localhost:3000');
});

Résumé des différences entre Cookie et Session :

  • Stockage : Les cookies sont stockés côté client (dans le navigateur), tandis que les sessions sont stockées côté serveur.
  • Sécurité : Les sessions sont généralement considérées comme plus sûres pour les données sensibles car les données ne quittent jamais le serveur. Les cookies peuvent être interceptés ou modifiés (sauf s'ils sont signés ou chiffrés manuellement).
  • Taille : Les cookies ont une taille limitée (environ 4KB), tandis que les sessions peuvent stocker des quantités de données plus importantes.
  • Utilisation : Les cookies sont souvent utilisés pour de petites informations persistantes (langue, thème, ID de suivi). Les sessions sont idéales pour la gestion de l'état de connexion, les paniers d'achat, ou des données utilisateur complexes.

2.5. Vérification humaine avec Svg-Captcha

Le middleware svg-captcha génère des images de CAPTCHA au format SVG. Ces images contiennent du texte aléatoire avec des distorsions et des lignes de bruit, aidant à distinguer les utilisateurs humains des bots.

Installez le middleware :

npm install svg-captcha

Utilisation dans une route Express :

const captcha = require('svg-captcha');
const express = require('express');
const session = require('express-session'); // Nécessaire pour stocker le texte du captcha
const app = express();

// Configurer express-session (voir section 2.4 pour plus de détails)
app.use(session({
  secret: 'maCleSecretePourCaptcha',
  resave: false,
  saveUninitialized: true,
  cookie: { maxAge: 600000 } // Cookie de session valide pendant 10 minutes
}));

// Route pour générer et servir le CAPTCHA
app.get('/generer-captcha', (req, res) => {
  const optionsCaptcha = {
    size: 5,        // Longueur du CAPTCHA
    noise: 3,       // Nombre de lignes de bruit
    color: true,    // Les caractères ont des couleurs aléatoires
    background: '#f0f0f0', // Couleur de fond
    width: 150,     // Largeur de l'image
    height: 60,     // Hauteur de l'image
    fontSize: 50,   // Taille de la police
    ignoreChars: '0o1i', // Caractères à exclure pour éviter la confusion
  };

  const captchaImage = captcha.create(optionsCaptcha);

  // Stocke le texte du CAPTCHA dans la session pour vérification ultérieure
  req.session.captchaText = captchaImage.text;

  // Envoie l'image SVG au client
  res.type('svg'); // Définit le Content-Type à image/svg+xml
  res.send(captchaImage.data);
});

// Route de validation du CAPTCHA (exemple avec un formulaire POST)
app.post('/verifier-captcha', express.urlencoded({ extended: true }), (req, res) => {
  const texteUtilisateur = req.body.captchaInput; // Supposons un champ 'captchaInput'
  const texteSession = req.session.captchaText;

  if (texteUtilisateur && texteSession && texteUtilisateur.toLowerCase() === texteSession.toLowerCase()) {
    // CAPTCHA correct
    delete req.session.captchaText; // Supprime le CAPTCHA après utilisation
    res.send('CAPTCHA validé avec succès !');
  } else {
    // CAPTCHA incorrect ou manquant
    res.status(400).send('CAPTCHA incorrect ou manquant. Veuillez réessayer. <a href="/">Retour</a>');
  }
});

// Route racine pour un formulaire d'exemple
app.get('/', (req, res) => {
  res.send(`
    <h1>Exemple de CAPTCHA</h1>
    <form action="/verifier-captcha" method="post">
      <img src="/generer-captcha" alt="Code de vérification" style="vertical-align: middle;"> <button type="button" onclick="document.querySelector('img').src = '/generer-captcha?' + Date.now();">Actualiser</button><br><br>
      <label for="captchaInput">Entrez le code ci-dessus :</label><br>
      <input type="text" id="captchaInput" name="captchaInput" required><br><br>
      <button type="submit">Soumettre</button>
    </form>
  `);
});

// Démarrez le serveur
app.listen(3000, () => {
  console.log('Serveur démarré sur http://localhost:3000');
});

Côté frontend, vous pouvez afficher le CAPTCHA simplement avec une balise <img> pointant vers votre route de génération de CAPTCHA. Il est recommandé d'ajouter un bouton pour rafraîchir le CAPTCHA si l'utilisateur ne peut pas le lire.

Étiquettes: Express.js Node.js middleware express-generator Nodemon

Publié le 20 juillet à 20h58