Node.js§

Node.js est un environnement d’exécution JavaScript côté serveur, construit sur le moteur V8 de Chrome. Il permet d’exécuter du JavaScript en dehors du navigateur.

Caractéristiques§

Architecture événementielle non-bloquante : Node.js utilise un modèle d’I/O asynchrone basé sur une boucle d’événements (event loop). Au lieu de créer un thread par connexion, Node.js gère toutes les connexions dans un seul thread, en déléguant les opérations I/O au système d’exploitation.

Requêtes entrantes


   Event Loop (single thread)

   ┌────┴────┐
   │         │
Callbacks  I/O Operations (async)
(sync)    ─────────────────────────
          File System, Network, DB
          (gérés par libuv en C++)

Adapté pour : APIs REST, applications temps réel (chat, streaming), microservices, outils CLI.

Moins adapté pour : calcul intensif CPU (bloque l’event loop), applications nécessitant du parallélisme natif.

npm — Node Package Manager§

# Initialiser un projet
npm init -y

# Installer une dépendance
npm install express
npm install -D nodemon   # dépendance de développement (-D = --save-dev)

# Désinstaller
npm uninstall express

# Lancer un script (défini dans package.json)
npm start
npm run dev

# Audit de sécurité
npm audit
npm audit fix

Modules CommonJS et ESM§

// CommonJS (historique, .js par défaut si "type": "commonjs")
const fs = require('fs');
const { join } = require('path');
module.exports = { maFonction };

// ESM — ES Modules (.mjs ou "type": "module" dans package.json)
import fs from 'fs';
import { join } from 'path';
export { maFonction };
export default class MaClasse {}

Modules natifs courants§

const fs = require('fs');           // Système de fichiers
const path = require('path');       // Manipulation de chemins
const http = require('http');       // Serveur HTTP natif
const https = require('https');     // Serveur HTTPS natif
const os = require('os');           // Informations système
const crypto = require('crypto');   // Cryptographie
const events = require('events');   // EventEmitter
const stream = require('stream');   // Streams
const url = require('url');         // Parsing d'URL
const querystring = require('querystring'); // Query strings

Système de fichiers asynchrone§

const fs = require('fs').promises;  // API promises (Node 10+)
const path = require('path');

// Lire un fichier
const contenu = await fs.readFile(path.join(__dirname, 'data.txt'), 'utf8');

// Écrire un fichier
await fs.writeFile('output.txt', 'Hello World', 'utf8');

// Lire un répertoire
const fichiers = await fs.readdir('./');

// Vérifier l'existence
try {
    await fs.access('fichier.txt');
    console.log('Existe');
} catch {
    console.log("N'existe pas");
}

// Copier, renommer, supprimer
await fs.copyFile('src.txt', 'dest.txt');
await fs.rename('ancien.txt', 'nouveau.txt');
await fs.unlink('fichier.txt');

Streams§

Les streams permettent de traiter des données en flux continu sans charger tout le fichier en mémoire.

const fs = require('fs');

// Lire un grand fichier par morceaux
const readable = fs.createReadStream('grand-fichier.csv', { encoding: 'utf8' });

readable.on('data', (chunk) => {
    console.log(`Reçu ${chunk.length} octets`);
});
readable.on('end', () => console.log('Lecture terminée'));
readable.on('error', (err) => console.error(err));

// Pipe : connecter readable → writable
const readStream = fs.createReadStream('input.txt');
const writeStream = fs.createWriteStream('output.txt');
readStream.pipe(writeStream);

// Transform stream (transformer à la volée)
const { Transform } = require('stream');
const majuscules = new Transform({
    transform(chunk, encoding, callback) {
        callback(null, chunk.toString().toUpperCase());
    }
});
fs.createReadStream('input.txt')
    .pipe(majuscules)
    .pipe(fs.createWriteStream('output.txt'));

EventEmitter§

const EventEmitter = require('events');

class Serveur extends EventEmitter {
    demarrer(port) {
        // ... démarrage
        this.emit('demarrage', port);
    }
    arreter() {
        this.emit('arret');
    }
}

const serveur = new Serveur();
serveur.on('demarrage', (port) => console.log(`Serveur démarré sur :${port}`));
serveur.on('arret', () => console.log('Serveur arrêté'));
serveur.demarrer(3000);

Express.js§

Express est le framework web minimal pour Node.js. Il fournit un système de routage, un middleware pipeline et des utilitaires HTTP.

Installation et serveur de base§

const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;

// Middleware globaux
app.use(express.json());                    // Parser les corps JSON
app.use(express.urlencoded({ extended: true })); // Parser les form data
app.use(express.static('public'));          // Servir des fichiers statiques

// Route simple
app.get('/', (req, res) => {
    res.json({ message: 'Bienvenue sur l\'API' });
});

// Démarrer le serveur
app.listen(PORT, () => {
    console.log(`Serveur sur http://localhost:${PORT}`);
});

Routage§

// Méthodes HTTP
app.get('/clients', listerClients);
app.post('/clients', creerClient);
app.get('/clients/:id', obtenirClient);
app.put('/clients/:id', remplacerClient);
app.patch('/clients/:id', modifierClient);
app.delete('/clients/:id', supprimerClient);

// Paramètres de route
app.get('/clients/:id/commandes/:commandeId', (req, res) => {
    const { id, commandeId } = req.params;
    res.json({ clientId: id, commandeId });
});

// Query parameters
app.get('/produits', (req, res) => {
    const { page = 1, limit = 20, categorie } = req.query;
    // GET /produits?page=2&limit=10&categorie=informatique
    res.json({ page, limit, categorie });
});

// Router modulaire
const router = express.Router();
router.get('/', listerClients);
router.post('/', creerClient);
router.get('/:id', obtenirClient);

app.use('/api/v1/clients', router);

Middleware§

Le middleware est une fonction (req, res, next) qui s’exécute dans le pipeline de traitement de la requête.

// Middleware global (s'applique à toutes les routes)
app.use((req, res, next) => {
    console.log(`${req.method} ${req.path} - ${new Date().toISOString()}`);
    next(); // Passer au middleware suivant
});

// Middleware sur une route spécifique
const verifierAuthentification = (req, res, next) => {
    const token = req.headers.authorization?.split(' ')[1];
    if (!token) {
        return res.status(401).json({ erreur: 'Token manquant' });
    }
    try {
        req.utilisateur = jwt.verify(token, process.env.JWT_SECRET);
        next();
    } catch {
        res.status(401).json({ erreur: 'Token invalide' });
    }
};

app.get('/profil', verifierAuthentification, (req, res) => {
    res.json(req.utilisateur);
});

// Middleware de gestion des erreurs (4 paramètres)
app.use((err, req, res, next) => {
    console.error(err.stack);
    res.status(err.status || 500).json({
        erreur: {
            message: err.message || 'Erreur interne du serveur',
            code: err.code || 'INTERNAL_ERROR'
        }
    });
});

Architecture MVC avec Express§

projet/
├── src/
│   ├── controllers/      # Logique de traitement des requêtes
│   │   └── clientController.js
│   ├── services/         # Logique métier
│   │   └── clientService.js
│   ├── models/           # Modèles de données (Mongoose, Sequelize...)
│   │   └── Client.js
│   ├── routes/           # Définition des routes
│   │   └── clientRoutes.js
│   ├── middlewares/      # Middlewares personnalisés
│   │   └── auth.js
│   └── app.js            # Configuration Express
├── .env                  # Variables d'environnement
└── server.js             # Point d'entrée
// controllers/clientController.js
const clientService = require('../services/clientService');

exports.lister = async (req, res, next) => {
    try {
        const { page = 1, limit = 20 } = req.query;
        const clients = await clientService.lister({ page, limit });
        res.json({ data: clients });
    } catch (err) {
        next(err);  // Passer l'erreur au middleware d'erreur
    }
};

exports.creer = async (req, res, next) => {
    try {
        const client = await clientService.creer(req.body);
        res.status(201).json({ data: client });
    } catch (err) {
        next(err);
    }
};

// routes/clientRoutes.js
const router = require('express').Router();
const { lister, creer, obtenir, modifier, supprimer } = require('../controllers/clientController');
const { verifierToken } = require('../middlewares/auth');

router.get('/', lister);
router.post('/', verifierToken, creer);
router.get('/:id', obtenir);
router.patch('/:id', verifierToken, modifier);
router.delete('/:id', verifierToken, supprimer);

module.exports = router;

Validation avec express-validator§

const { body, validationResult } = require('express-validator');

const reglesCréationClient = [
    body('nom')
        .notEmpty().withMessage('Le nom est requis')
        .isLength({ min: 2, max: 100 }).withMessage('2 à 100 caractères'),
    body('email')
        .isEmail().withMessage('Email invalide')
        .normalizeEmail(),
    body('age')
        .optional()
        .isInt({ min: 0, max: 150 }).withMessage('Âge entre 0 et 150')
];

const valider = (req, res, next) => {
    const erreurs = validationResult(req);
    if (!erreurs.isEmpty()) {
        return res.status(422).json({ erreurs: erreurs.array() });
    }
    next();
};

router.post('/', reglesCréationClient, valider, creerClient);

Variables d’environnement avec dotenv§

# .env (ne jamais committer)
PORT=3000
DATABASE_URL=postgresql://user:pass@localhost:5432/madb
JWT_SECRET=super_secret_key_changez_moi
NODE_ENV=development
require('dotenv').config();  // À appeler le plus tôt possible

const port = process.env.PORT || 3000;
const dbUrl = process.env.DATABASE_URL;

CORS§

const cors = require('cors');

// Autoriser toutes les origines (développement seulement)
app.use(cors());

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

Authentification JWT§

const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');

// Connexion
app.post('/auth/login', async (req, res) => {
    const { email, motDePasse } = req.body;
    const utilisateur = await trouverParEmail(email);

    if (!utilisateur || !await bcrypt.compare(motDePasse, utilisateur.motDePasse)) {
        return res.status(401).json({ erreur: 'Identifiants invalides' });
    }

    const token = jwt.sign(
        { id: utilisateur.id, email: utilisateur.email, role: utilisateur.role },
        process.env.JWT_SECRET,
        { expiresIn: '24h' }
    );

    res.json({ token });
});

// Inscription
app.post('/auth/inscription', async (req, res) => {
    const { email, motDePasse } = req.body;
    const hash = await bcrypt.hash(motDePasse, 12);  // 12 rounds
    const utilisateur = await creerUtilisateur({ email, motDePasse: hash });
    res.status(201).json({ id: utilisateur.id });
});

Connexion à une base de données (PostgreSQL avec pg)§

const { Pool } = require('pg');

const pool = new Pool({
    connectionString: process.env.DATABASE_URL,
    max: 20,              // Taille maximale du pool
    idleTimeoutMillis: 30000,
    connectionTimeoutMillis: 2000
});

// Requête simple
const { rows } = await pool.query(
    'SELECT * FROM clients WHERE id = $1',
    [id]
);

// Transaction
const client = await pool.connect();
try {
    await client.query('BEGIN');
    await client.query('UPDATE comptes SET solde = solde - $1 WHERE id = $2', [montant, idSource]);
    await client.query('UPDATE comptes SET solde = solde + $1 WHERE id = $2', [montant, idDest]);
    await client.query('COMMIT');
} catch (err) {
    await client.query('ROLLBACK');
    throw err;
} finally {
    client.release();
}

Middlewares tiers populaires§

PackageUsage
corsGestion du Cross-Origin Resource Sharing
helmetEn-têtes de sécurité HTTP
express-rate-limitLimitation du nombre de requêtes
morganLogging des requêtes HTTP
compressionCompression gzip des réponses
multerUpload de fichiers multipart
express-validatorValidation et sanitisation
dotenvVariables d’environnement
jsonwebtokenCréation et vérification JWT
bcryptHachage de mots de passe
pgClient PostgreSQL
mongooseODM MongoDB
sequelizeORM SQL
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
const morgan = require('morgan');

// Sécurité
app.use(helmet());

// Rate limiting
const limiter = rateLimit({
    windowMs: 15 * 60 * 1000,  // 15 minutes
    max: 100,                    // 100 requêtes par fenêtre
    message: 'Trop de requêtes, réessayez dans 15 minutes'
});
app.use('/api/', limiter);

// Logging
app.use(morgan('combined'));   // Format Apache
app.use(morgan('dev'));        // Format compact coloré