1. Qu'est-ce qu'un middleware ?

Un middleware est une fonction qui s'exécute au milieu de la chaîne de traitement d'une requête, entre sa réception et l'envoi de la réponse. C'est l'un des concepts centraux d'Express.

Signature d'un middleware

// Middleware standard : 3 paramètres
function monMiddleware(req, res, next) {
  // req  : requête entrante
  // res  : réponse sortante
  // next : fonction pour passer au middleware suivant

  console.log('Je suis un middleware !');

  // OBLIGATOIRE : appeler next() ou envoyer une réponse
  // Si on n'appelle pas next(), la chaîne s'arrête ici
  next();
}

// Middleware d'erreur : 4 paramètres (doit être en dernier !)
function errorMiddleware(err, req, res, next) {
  console.error(err.message);
  res.status(500).json({ error: err.message });
}

Flux d'exécution

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

// Requête → MW1 → MW2 → Route Handler → Réponse

app.use((req, res, next) => {
  console.log('MW1 : avant');
  next();                       // Passe au suivant
  console.log('MW1 : après');  // Exécuté APRÈS la réponse
});

app.use((req, res, next) => {
  console.log('MW2 : exécuté');
  req.monDonnee = 'valeur ajoutée par MW2'; // Enrichir req
  next();
});

app.get('/', (req, res) => {
  console.log('Handler route');
  res.json({ data: req.monDonnee }); // → { data: "valeur ajoutée par MW2" }
});

app.listen(3000);

next(err) — Passer une erreur

app.use((req, res, next) => {
  try {
    // Si une erreur survient...
    if (!req.headers.authorization) {
      throw new Error('Token manquant');
    }
    next(); // Tout va bien
  } catch (err) {
    next(err); // Passer l'erreur au error handler
  }
});
💡 Les middlewares sont exécutés dans l'ordre de leur déclaration. L'ordre dans le code compte énormément.

2. app.use() et middlewares intégrés

app.use() monte un middleware global ou sur un préfixe de chemin. Express embarque plusieurs middlewares très utiles directement.

Middlewares intégrés à Express

const express = require('express');
const path    = require('path');
const app     = express();

// ── express.json() ─────────────────────────────────────
// Parse les body JSON → disponible dans req.body
// Content-Type doit être : application/json
app.use(express.json());

// ── express.urlencoded() ───────────────────────────────
// Parse les body de formulaires HTML
// Content-Type : application/x-www-form-urlencoded
app.use(express.urlencoded({ extended: true }));

// ── express.static() ───────────────────────────────────
// Sert les fichiers statiques d'un dossier
// GET /style.css → lit public/style.css
app.use(express.static(path.join(__dirname, 'public')));

// ── express.static() avec préfixe ─────────────────────
// GET /assets/logo.png → lit images/logo.png
app.use('/assets', express.static(path.join(__dirname, 'images')));

app.listen(3000);

app.use() avec préfixe de chemin

// Middleware uniquement pour les routes /api/...
app.use('/api', (req, res, next) => {
  console.log('Requête vers /api :', req.url);
  next();
});

// Monter un router sur un préfixe (voir module N07)
// const usersRouter = require('./routes/users');
// app.use('/api/users', usersRouter);

Ordre : middlewares AVANT routes

// ✅ CORRECT — express.json() avant la route qui utilise req.body
app.use(express.json());

app.post('/data', (req, res) => {
  console.log(req.body); // { name: "Alice" }
  res.json({ received: req.body });
});

// ❌ INCORRECT — express.json() après la route
// app.post('/data', handler);
// app.use(express.json()); // req.body sera undefined dans handler !
⚠️ express.json() ne parse que les requêtes avec Content-Type: application/json. Pour les formulaires HTML, utilisez express.urlencoded().

3. Middlewares custom

Vous pouvez créer vos propres middlewares pour factoriser des comportements transversaux : logging, timing, authentification, validation, etc.

Middleware 1 — Logger de requêtes

// logger.js — Middleware de logging
const logger = (req, res, next) => {
  const start = Date.now();
  const timestamp = new Date().toISOString();

  // On écoute 'finish' pour logger APRÈS la réponse
  res.on('finish', () => {
    const duration = Date.now() - start;
    const color = res.statusCode < 400 ? '\x1b[32m' : '\x1b[31m'; // vert/rouge
    console.log(
      `${timestamp} ${color}${req.method}\x1b[0m ${req.path} → ${res.statusCode} (${duration}ms)`
    );
  });

  next(); // Ne pas oublier !
};

module.exports = logger;

Middleware 2 — Timer (req.startTime)

// Ajouter une propriété personnalisée à req
const timer = (req, res, next) => {
  req.startTime = Date.now();      // Enrichit l'objet req

  // Les handlers suivants peuvent utiliser req.startTime
  next();
};

// Utilisation dans une route
app.use(timer);
app.get('/slow', (req, res) => {
  const elapsed = Date.now() - req.startTime;
  res.json({ elapsed: elapsed + 'ms', message: 'Réponse rapide' });
});

Middleware 3 — Validation de header

// Vérifier la présence d'un header requis
const requireContentType = (req, res, next) => {
  // Uniquement pour les requêtes avec body (POST, PUT, PATCH)
  if (['POST', 'PUT', 'PATCH'].includes(req.method)) {
    const ct = req.headers['content-type'] ?? '';
    if (!ct.includes('application/json')) {
      return res.status(415).json({
        error: 'Content-Type: application/json requis',
      });
    }
  }
  next();
};

app.use(express.json());
app.use(requireContentType);

app.post('/users', (req, res) => {
  res.status(201).json({ created: req.body });
});
💡 En ajoutant des propriétés sur req (comme req.user, req.startTime), vous pouvez partager des données entre middlewares et handlers de façon propre.

4. Middleware d'erreur

Le middleware d'erreur a une signature spéciale à 4 paramètres : (err, req, res, next). Il doit être déclaré en dernier, après toutes les routes.

Déclenchement avec next(err)

const express = require('express');
const app = express();
app.use(express.json());

// Route qui peut déclencher une erreur
app.get('/users/:id', (req, res, next) => {
  const id = parseInt(req.params.id);

  if (isNaN(id)) {
    // next(err) déclenche le error handler
    return next(new Error('ID invalide — doit être un nombre'));
  }

  const user = { id, name: 'Alice' };
  res.json({ data: user });
});

// Erreur dans un handler async
app.get('/async-route', async (req, res, next) => {
  try {
    // Simuler une erreur asynchrone
    throw new Error('Erreur asynchrone !');
  } catch (err) {
    next(err); // Toujours passer l'erreur à next()
  }
});

Error handler centralisé complet

// Classe d'erreur personnalisée
class AppError extends Error {
  constructor(message, status = 500) {
    super(message);
    this.status  = status;
    this.name    = 'AppError';
  }
}

// Handler 404 — toutes les routes non trouvées
app.use('*', (req, res, next) => {
  next(new AppError(`Route "${req.path}" introuvable`, 404));
});

// Error handler — DOIT être en dernier avec 4 paramètres
app.use((err, req, res, next) => {
  const status  = err.status  || 500;
  const message = err.message || 'Erreur interne du serveur';

  // Logger l'erreur côté serveur
  if (status >= 500) {
    console.error('\x1b[31mERREUR 500:\x1b[0m', err.stack);
  }

  res.status(status).json({
    success: false,
    error: {
      message,
      status,
      // Ne pas exposer le stack en production
      ...(process.env.NODE_ENV !== 'production' && { stack: err.stack }),
    },
  });
});

// Utilisation
app.get('/secure', (req, res, next) => {
  if (!req.headers.authorization) {
    return next(new AppError('Non autorisé', 401));
  }
  res.json({ data: 'Données sécurisées' });
});

app.listen(3000);
⚠️ Express détecte le middleware d'erreur grâce à ses 4 paramètres. Si vous oubliez err ou next, Express le traitera comme un middleware normal et les erreurs ne seront pas interceptées.

5. Middlewares tiers

L'écosystème npm propose de nombreux middlewares Express prêts à l'emploi. Les plus utilisés sont morgan (logging) et cors (CORS).

morgan — Logging HTTP

// Installation : npm install morgan
const express = require('express');
const morgan  = require('morgan');
const app     = express();

// Formats prédéfinis
app.use(morgan('dev'));      // Format coloré pour le développement
// app.use(morgan('tiny')); // Format minimal
// app.use(morgan('combined')); // Format Apache standard (prod)

// Format personnalisé
app.use(morgan(':method :url :status :response-time ms'));

// Morgan avec condition (skip en production par ex.)
app.use(morgan('dev', {
  skip: (req) => req.url === '/health', // Ignorer la route health
}));

app.get('/users', (req, res) => res.json({ data: [] }));
app.listen(3000);

cors — Cross-Origin Resource Sharing

// Installation : npm install cors
const express = require('express');
const cors    = require('cors');
const app     = express();

// Autoriser toutes les origines (déconseillé en prod)
app.use(cors());

// Configuration précise pour la production
app.use(cors({
  origin: ['https://monsite.com', 'https://admin.monsite.com'],
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true,  // Autoriser les cookies
}));

// CORS sur une seule route
app.get('/public', cors(), (req, res) => {
  res.json({ data: 'Accessible depuis partout' });
});

Serveur complet avec morgan + cors

const express = require('express');
const morgan  = require('morgan');
const cors    = require('cors');
const app     = express();

// ── Stack de middlewares ──────────────────────────────
app.use(morgan('dev'));             // Logging
app.use(cors());                    // CORS
app.use(express.json());            // Body parser

// ── Routes ───────────────────────────────────────────
app.get('/', (req, res) => {
  res.json({ status: 'OK', message: 'API opérationnelle' });
});

app.post('/echo', (req, res) => {
  res.json({ received: req.body, timestamp: new Date().toISOString() });
});

// ── 404 ──────────────────────────────────────────────
app.use('*', (req, res) => {
  res.status(404).json({ error: 'Route introuvable', path: req.path });
});

// ── Error handler ─────────────────────────────────────
app.use((err, req, res, next) => {
  res.status(err.status || 500).json({ error: err.message });
});

app.listen(3000, () => console.log('Serveur avec morgan + cors sur :3000'));
💡 helmet (npm install helmet) est un autre middleware essentiel en production : il sécurise les headers HTTP (CSP, HSTS, X-Frame-Options...). Il sera détaillé dans le module N11 — Sécurité & Production.
← Accueil Exercices →