1. Installation et premier serveur Express
Express est le framework web Node.js le plus populaire.
Il ajoute une couche d'abstraction au-dessus du module http natif,
rendant le code beaucoup plus concis et lisible.
Installation
# Dans votre dossier de projet
npm init -y
npm install express
# Pour le développement (rechargement automatique)
npm install --save-dev nodemon
Premier serveur Express — 5 lignes
const express = require('express');
const app = express();
app.get('/', (req, res) => res.send('Hello Express !'));
app.listen(3000, () => console.log('Serveur Express sur http://localhost:3000'));
Comparaison : HTTP natif vs Express
| Fonctionnalité | HTTP natif | Express |
|---|---|---|
| Lire le body JSON | req.on('data'...) + parse |
express.json() → req.body |
| Paramètre d'URL | RegExp + groups | :id → req.params.id |
| Réponse JSON | res.writeHead + JSON.stringify |
res.json(data) |
| Status code | res.writeHead(404) |
res.status(404).json(...) |
| Routing | if/else manuels | app.get(path, handler) |
2. Méthodes HTTP — app.get / post / put / delete
Express expose une méthode pour chaque verbe HTTP.
Le handler reçoit toujours (req, res) en paramètres.
Les 4 méthodes CRUD
const express = require('express');
const app = express();
app.use(express.json()); // Parser le body JSON
// GET — Lire
app.get('/users', (req, res) => {
res.json({ method: 'GET', action: 'Lister les utilisateurs' });
});
// POST — Créer
app.post('/users', (req, res) => {
res.status(201).json({ method: 'POST', received: req.body });
});
// PUT — Remplacer
app.put('/users/:id', (req, res) => {
res.json({ method: 'PUT', id: req.params.id, received: req.body });
});
// DELETE — Supprimer
app.delete('/users/:id', (req, res) => {
res.json({ method: 'DELETE', id: req.params.id });
});
app.listen(3000);
app.all() et app.use()
// app.all() — Toutes les méthodes pour un chemin
app.all('/ping', (req, res) => {
res.json({ method: req.method, message: 'pong' });
});
// app.use() — Middleware pour tous les chemins
// (détaillé dans N06 — Middleware)
app.use((req, res, next) => {
console.log(`${req.method} ${req.url}`); // Logger
next(); // Passer au handler suivant
});
Handlers multiples sur la même route
// Plusieurs callbacks sur une route (chaîne de handlers)
function logger(req, res, next) {
console.log('Logger middleware');
next(); // Obligatoire pour passer au suivant
}
function checkAuth(req, res, next) {
if (!req.headers.authorization) {
return res.status(401).json({ error: 'Non autorisé' });
}
next();
}
// Les handlers sont exécutés dans l'ordre
app.get('/protected', logger, checkAuth, (req, res) => {
res.json({ message: 'Route protégée !' });
});
app.get → GET, app.post → POST, app.put → PUT,
app.delete → DELETE, app.patch → PATCH.
3. req — Paramètres de la requête
L'objet req d'Express enrichit celui du module http natif
avec des propriétés pratiques pour accéder aux données de la requête.
req.params — Paramètres de route dynamiques
// :id est un paramètre de route — capturé dans req.params.id
app.get('/users/:id', (req, res) => {
const { id } = req.params;
res.json({ userId: id, type: typeof id }); // id est toujours une string !
});
// Plusieurs paramètres
app.get('/users/:userId/posts/:postId', (req, res) => {
const { userId, postId } = req.params;
res.json({ userId, postId });
});
// :id? — paramètre optionnel
app.get('/products/:category?', (req, res) => {
const cat = req.params.category ?? 'all';
res.json({ category: cat });
});
req.query — Query string
// URL : GET /search?q=node&page=2&limit=10
app.get('/search', (req, res) => {
const { q = '', page = '1', limit = '10' } = req.query;
// req.query retourne toujours des strings !
const pageNum = parseInt(page, 10);
const limitNum = parseInt(limit, 10);
res.json({
query: q,
page: pageNum,
limit: limitNum,
offset: (pageNum - 1) * limitNum,
});
});
req.body — Corps de la requête
// Nécessite le middleware express.json() !
app.use(express.json());
app.post('/users', (req, res) => {
// req.body est l'objet JavaScript parsé
const { name, email, age } = req.body;
if (!name || !email) {
return res.status(400).json({ error: 'name et email requis' });
}
// Validation de type
if (age !== undefined && typeof age !== 'number') {
return res.status(400).json({ error: 'age doit être un nombre' });
}
res.status(201).json({ created: { name, email, age } });
});
Autres propriétés utiles
app.get('/info', (req, res) => {
res.json({
method: req.method, // "GET"
path: req.path, // "/info" (sans query string)
url: req.url, // "/info?foo=bar" (avec query)
hostname: req.hostname, // "localhost"
ip: req.ip, // IP du client
protocol: req.protocol, // "http" ou "https"
secure: req.secure, // true si HTTPS
userAgent: req.headers['user-agent'],
token: req.headers.authorization,
});
});
req.params, req.query retournent toujours des strings.
Convertissez avec parseInt(), parseFloat() ou Number() si nécessaire.
4. res — Construire la réponse
L'objet res d'Express simplifie énormément l'envoi de réponses
par rapport au module http natif.
Méthodes de réponse Express
| Méthode | Usage | Content-Type auto |
|---|---|---|
res.json(data) | Objet/tableau JSON | application/json |
res.send(string) | Texte ou HTML | text/html ou text/plain |
res.status(N).json() | JSON + status code | application/json |
res.redirect(url) | Redirection 302 | — |
res.sendFile(path) | Fichier statique | Détecté auto |
res.end() | Corps vide (204) | — |
Exemples de réponses
const path = require('path');
// JSON avec status 201 (Created)
app.post('/articles', (req, res) => {
const article = { id: 1, ...req.body };
res.status(201).json({ success: true, data: article });
});
// Erreur 404
app.get('/users/:id', (req, res) => {
const user = users.find(u => u.id === parseInt(req.params.id));
if (!user) {
return res.status(404).json({ error: 'Utilisateur non trouvé' });
}
res.json({ data: user });
});
// Redirection
app.get('/old-path', (req, res) => {
res.redirect(301, '/new-path'); // 301 = permanent
});
// Envoyer un fichier
app.get('/download', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'rapport.pdf'));
});
// Réponse vide (204 No Content — souvent pour DELETE)
app.delete('/sessions', (req, res) => {
res.status(204).end(); // Pas de body
});
Chaînage avec .status()
// Pattern recommandé : status().json() chaîné
res.status(200).json({ success: true, data: items }); // OK
res.status(201).json({ success: true, data: newItem }); // Created
res.status(400).json({ success: false, error: 'Bad Request' });
res.status(401).json({ success: false, error: 'Unauthorized' });
res.status(404).json({ success: false, error: 'Not Found' });
res.status(500).json({ success: false, error: 'Internal Error' });
res.json() sans .status() envoie automatiquement un 200.
Soyez explicite avec les codes non-200 pour respecter les conventions REST.
5. Structure d'une application Express
L'ordre des déclarations dans une application Express est crucial. Les middlewares et routes sont traités séquentiellement, de haut en bas.
Application complète bien organisée
const express = require('express');
const app = express();
// ─────────────────────────────────────────────────────
// 1. MIDDLEWARES GLOBAUX — avant toutes les routes
// ─────────────────────────────────────────────────────
app.use(express.json()); // Parser JSON body
app.use(express.urlencoded({ extended: true })); // Parser form data
// Logger de requêtes (middleware custom simple)
app.use((req, res, next) => {
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
next();
});
// ─────────────────────────────────────────────────────
// 2. ROUTES — dans l'ordre du plus spécifique au plus général
// ─────────────────────────────────────────────────────
let users = [
{ id: 1, name: 'Alice', email: 'alice@example.com' },
{ id: 2, name: 'Bob', email: 'bob@example.com' },
];
let nextId = 3;
app.get('/users', (req, res) => {
res.json({ success: true, count: users.length, data: users });
});
app.get('/users/:id', (req, res) => {
const user = users.find(u => u.id === parseInt(req.params.id));
if (!user) return res.status(404).json({ error: 'Non trouvé' });
res.json({ success: true, data: user });
});
app.post('/users', (req, res) => {
const { name, email } = req.body;
if (!name || !email) return res.status(400).json({ error: 'name et email requis' });
const user = { id: nextId++, name, email };
users.push(user);
res.status(201).json({ success: true, data: user });
});
app.put('/users/:id', (req, res) => {
const idx = users.findIndex(u => u.id === parseInt(req.params.id));
if (idx === -1) return res.status(404).json({ error: 'Non trouvé' });
users[idx] = { ...users[idx], ...req.body, id: users[idx].id };
res.json({ success: true, data: users[idx] });
});
app.delete('/users/:id', (req, res) => {
const idx = users.findIndex(u => u.id === parseInt(req.params.id));
if (idx === -1) return res.status(404).json({ error: 'Non trouvé' });
users.splice(idx, 1);
res.json({ success: true, message: 'Supprimé' });
});
// ─────────────────────────────────────────────────────
// 3. 404 — DOIT être après toutes les routes
// ─────────────────────────────────────────────────────
app.use('*', (req, res) => {
res.status(404).json({ error: 'Route introuvable', path: req.path });
});
// ─────────────────────────────────────────────────────
// 4. ERROR HANDLER — DOIT être en dernier (4 paramètres)
// ─────────────────────────────────────────────────────
app.use((err, req, res, next) => {
console.error(err);
res.status(500).json({ error: 'Erreur interne', message: err.message });
});
app.listen(3000, () => console.log('API Users sur http://localhost:3000'));
Ordre des éléments — résumé
| # | Élément | Raison de l'ordre |
|---|---|---|
| 1 | Middlewares globaux (express.json(), logger...) | Doivent traiter la req avant les routes |
| 2 | Routes spécifiques (/users/:id avant /users) | Express choisit la première route qui correspond |
| 3 | Handler 404 (app.use('*', ...)) | Capte tout ce qui n'a pas matché |
| 4 | Error handler ((err, req, res, next)) | Reçoit les erreurs propagées avec next(err) |
express.json() est placé après vos routes, req.body sera undefined dans ces routes. Toujours déclarer les middlewares avant les routes qui en dépendent.