Back-end · Full-stack

Node.js

JavaScript côté serveur — event loop, npm, Express, API REST, fichiers, bases de données, JWT et déploiement. Pour étudiants qui connaissent déjà JS basique.

C'est quoi Node.js ?

Node.js est un environnement d'exécution JavaScript hors du navigateur, construit sur le moteur V8 de Chrome. Il permet d'écrire du code serveur en JavaScript — lire des fichiers, ouvrir des sockets, répondre à des requêtes HTTP — tout ce qu'un navigateur ne peut pas faire.

Ton code Node.js
Node.js API (fs, http, path, crypto…)
libuv — event loop + I/O asynchrone
V8 — moteur JS de Chrome
Système d'exploitation (Linux / Windows / macOS)
💡

Comparaison Python : Node.js est à JavaScript ce que CPython est à Python — l'interpréteur qui fait tourner le code. La différence clé : Node est non-bloquant par défaut grâce à son event loop.

L'event loop — le cœur de Node

Call Stack
Pile d'appels
Exécute le code synchrone, une fonction à la fois.
Web APIs / libuv
I/O en parallèle
Lire fichier, requête réseau, timer — sans bloquer la pile.
Callback Queue
File d'attente
Résultats prêts, en attente que la pile soit vide.
Event loop — exemple concret
// Synchrone — s'exécute immédiatement
console.log('1 — début');

// Asynchrone — délégué à libuv, callback mis en file
setTimeout(() => console.log('3 — après 0ms'), 0);

// Synchrone — s'exécute avant le callback
console.log('2 — fin du code sync');

// Affichage : 1 → 2 → 3
// "0ms" ne signifie pas "immédiat" — la pile doit être vide d'abord

// Conséquence : Node peut gérer des milliers de connexions
// simultanées sans créer un thread par connexion (contrairement
// à Apache). C'est pourquoi il est idéal pour les API REST.

// Installer Node.js : https://nodejs.org  (LTS recommandé)
// Vérifier :
node --version   // v20.x.x
npm --version    // 10.x.x

npm & package.json

Commandes npm essentielles
# Créer un nouveau projet
npm init -y            # -y = accepter tous les défauts

# Installer une dépendance (production)
npm install express
npm install express mysql2 jsonwebtoken dotenv

# Installer une dépendance de développement uniquement
npm install --save-dev nodemon jest

# Installer toutes les dépendances du projet (après git clone)
npm install

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

# Voir les packages installés
npm list --depth=0

# Mettre à jour
npm update express

# Supprimer
npm uninstall express
⚠️

Ne jamais committer node_modules/ sur Git. Ajouter node_modules dans .gitignore. Le dossier peut peser plusieurs centaines de MB.

package.json — fichier de configuration
{
  "name": "mon-api",
  "version": "1.0.0",
  "description": "API REST Express",
  "main": "src/index.js",

  "scripts": {
    "start":   "node src/index.js",
    "dev":     "nodemon src/index.js",  // ← rechargement auto
    "test":    "jest"
  },

  "dependencies": {
    "express":      "^4.19.2",
    "mysql2":       "^3.9.4",
    "jsonwebtoken": "^9.0.2",
    "bcryptjs":     "^2.4.3",
    "dotenv":       "^16.4.5",
    "cors":         "^2.8.5"
  },

  "devDependencies": {
    "nodemon": "^3.1.0",  // rechargement auto en dev
    "jest":    "^29.7.0"   // tests
  }
}

// package-lock.json = versions exactes installées
// → TOUJOURS committer ce fichier sur Git

Modules CommonJS & ESM

CommonJS — syntaxe historique de Node
// math.js — exporter
function additionner(a, b) { return a + b; }
function multiplier(a, b)  { return a * b; }

module.exports = { additionner, multiplier };
// ou : module.exports = { additionner, multiplier };

// main.js — importer
const { additionner, multiplier } = require('./math');
const express = require('express'); // module installé
const fs      = require('fs');       // module natif Node
const path    = require('path');     // module natif Node

console.log(additionner(2, 3));  // 5
ESM — syntaxe moderne (recommandée en 2024)
// Dans package.json : "type": "esm"
// OU renommer les fichiers en .mjs

// math.mjs — exporter
export function additionner(a, b) { return a + b; }
export default function calculer() { /* ... */ }

// main.mjs — importer
import { additionner } from './math.mjs';
import calculer       from './math.mjs';  // export default
import express        from 'express';
import fs             from 'fs';
import { readFile }   from 'fs/promises';
ℹ️

À la HEH, les cours utilisent souvent CommonJS (require) car il est encore majoritaire dans les tutos et projets existants. ESM est la direction de l'avenir mais les deux coexistent.

Serveur HTTP natif

Serveur HTTP minimal — sans framework
const http = require('http');

const serveur = http.createServer((req, res) => {
  // req = requête entrante (url, method, headers, body...)
  // res = réponse à envoyer

  console.log(`${req.method} ${req.url}`);

  if (req.url === '/' && req.method === 'GET') {
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ message: 'Bonjour !' }));

  } else if (req.url === '/utilisateurs') {
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify([{ id: 1, nom: 'Alice' }]));

  } else {
    res.writeHead(404);
    res.end(JSON.stringify({ erreur: 'Route introuvable' }));
  }
});

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

// Lancer : node index.js
// Tester : curl http://localhost:3000
//       ou navigateur / Postman / Thunder Client (VS Code)

Le module http natif est bas niveau — parfait pour comprendre ce qui se passe, mais vite verbeux pour des vraies API. En pratique on utilise Express qui simplifie tout ça. Comprendre le module http aide à déboguer Express.

Lire le body d'une requête POST
// Le body arrive par morceaux (streams) → collecter
const serveur = http.createServer((req, res) => {
  if (req.method === 'POST') {
    let body = '';

    req.on('data', chunk => {
      body += chunk.toString();  // accumuler
    });

    req.on('end', () => {
      const data = JSON.parse(body);
      console.log(data);         // objet JS
      res.writeHead(201);
      res.end(JSON.stringify({ recu: data }));
    });
  }
});
// Express fait ça automatiquement avec express.json()

Express — routing

Express — application de base
const express = require('express');
const app = express();

// Middleware global — parser le JSON automatiquement
app.use(express.json());
app.use(express.urlencoded({ extended: true })); // formulaires HTML

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

// Paramètre de route — :id
app.get('/utilisateurs/:id', (req, res) => {
  const { id } = req.params;         // URL : /utilisateurs/42
  res.json({ id: Number(id), nom: 'Alice' });
});

// Query string — ?page=2&limite=10
app.get('/articles', (req, res) => {
  const { page = 1, limite = 10 } = req.query;
  res.json({ page, limite });
});

// POST avec body JSON
app.post('/utilisateurs', (req, res) => {
  const { nom, email } = req.body;   // grâce à express.json()
  if (!nom || !email)
    return res.status(400).json({ erreur: 'Champs manquants' });
  res.status(201).json({ id: 1, nom, email });
});

app.put('/utilisateurs/:id',    (req, res) => { /* modifier */ });
app.delete('/utilisateurs/:id', (req, res) => { /* supprimer */ });

app.listen(3000, () => console.log('Port 3000'));
Router Express — organiser par ressource
// routes/utilisateurs.js — router dédié
const router = require('express').Router();

router.get('/',    listerUtilisateurs);
router.get('/:id', voirUtilisateur);
router.post('/',   creerUtilisateur);
router.put('/:id', modifierUtilisateur);
router.delete('/:id', supprimerUtilisateur);

module.exports = router;

// index.js — monter le router
const utilisateursRouter = require('./routes/utilisateurs');
app.use('/api/utilisateurs', utilisateursRouter);

// Résultat :
// GET    /api/utilisateurs      → listerUtilisateurs
// GET    /api/utilisateurs/42   → voirUtilisateur
// POST   /api/utilisateurs      → creerUtilisateur
// PUT    /api/utilisateurs/42   → modifierUtilisateur
// DELETE /api/utilisateurs/42   → supprimerUtilisateur
200
OK
GET réussi, PUT/PATCH réussi
201
Created
POST → ressource créée
204
No Content
DELETE réussi, pas de body
400
Bad Request
Body invalide, champ manquant
401
Unauthorized
Non authentifié (pas de token)
403
Forbidden
Authentifié mais sans droits
404
Not Found
Ressource inexistante
500
Server Error
Bug côté serveur

Middleware

Un middleware est une fonction qui s'exécute entre la réception de la requête et l'envoi de la réponse. Chaque middleware peut modifier req, modifier res, ou passer au suivant avec next().

REQ →
cors()
express.json()
logger
verifierToken()
Route handler
gestionErreurs()
→ RES
Créer un middleware personnalisé
// Structure d'un middleware : (req, res, next)
function logger(req, res, next) {
  const debut = Date.now();
  console.log(`→ ${req.method} ${req.url}`);

  res.on('finish', () => {
    const duree = Date.now() - debut;
    console.log(`← ${res.statusCode} (${duree}ms)`);
  });

  next(); // ← OBLIGATOIRE pour continuer la chaîne
}

// Middleware de validation
function validerUtilisateur(req, res, next) {
  const { nom, email } = req.body;
  if (!nom || !email) {
    return res.status(400).json({ erreur: 'nom et email requis' });
    // Si on return ici → next() n'est PAS appelé → chaîne stoppée
  }
  next();
}

// Appliquer globalement
app.use(logger);

// Appliquer sur une route spécifique
app.post('/utilisateurs', validerUtilisateur, creerUtilisateur);
Middleware CORS & gestion d'erreurs globale
const cors = require('cors');

// CORS — autoriser le front React/Vue à appeler l'API
app.use(cors({
  origin: 'http://localhost:5173',  // URL du front Vite/Vue
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
}));

// Gestion d'erreurs globale — 4 paramètres obligatoires !
app.use(function(err, req, res, next) {
  console.error(err.stack);
  res.status(err.status || 500).json({
    erreur: err.message || 'Erreur interne du serveur',
  });
});

// Déclencher depuis une route :
app.get('/erreur', (req, res, next) => {
  const err = new Error('Quelque chose a planté');
  err.status = 503;
  next(err); // passer l'erreur au middleware d'erreur
});
💡

L'ordre des app.use() est important — les middlewares s'exécutent dans l'ordre de déclaration. Le middleware d'erreurs (4 params) doit toujours être en dernier.

API REST complète — architecture MVC

mon-api/ ├── src/ │ ├── index.js ← point d'entrée │ ├── routes/ │ │ ├── articles.js │ │ └── utilisateurs.js │ ├── controllers/ │ │ ├── articlesCtrl.js ← logique métier │ │ └── authCtrl.js │ ├── models/ │ │ └── Article.js ← accès BDD │ ├── middleware/ │ │ ├── auth.js ← vérification JWT │ │ └── validation.js │ └── config/ │ └── db.js ← connexion BDD ├── .env ← variables secrètes ├── .gitignore ├── package.json └── package-lock.json
src/index.js — point d'entrée
require('dotenv').config();           // ← toujours en premier
const express = require('express');
const cors    = require('cors');

const articlesRouter     = require('./routes/articles');
const utilisateursRouter = require('./routes/utilisateurs');

const app = express();

// Middlewares globaux
app.use(cors());
app.use(express.json());

// Routes
app.use('/api/articles',     articlesRouter);
app.use('/api/utilisateurs', utilisateursRouter);

// Middleware 404 — route non trouvée
app.use((req, res) => res.status(404).json({ erreur: 'Non trouvé' }));

// Middleware d'erreur global
app.use((err, req, res, next) => {
  res.status(err.status || 500).json({ erreur: err.message });
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`API sur http://localhost:${PORT}`));
controllers/articlesCtrl.js
const Article = require('../models/Article');

exports.lister = async (req, res, next) => {
  try {
    const articles = await Article.findAll();
    res.json(articles);
  } catch (err) { next(err); }  // déléguer au middleware
};

exports.creer = async (req, res, next) => {
  try {
    const { titre, contenu } = req.body;
    const article = await Article.create({ titre, contenu,
      auteur_id: req.user.id });   // req.user injecté par JWT
    res.status(201).json(article);
  } catch (err) { next(err); }
};

Lire/écrire des fichiers — module fs

fs/promises — API async/await moderne
const fs   = require('fs/promises');
const path = require('path');

// Lire un fichier texte
async function lireFichier() {
  const contenu = await fs.readFile('data.txt', 'utf-8');
  console.log(contenu);
}

// Écrire (remplace le fichier existant)
await fs.writeFile('sortie.txt', 'Bonjour !', 'utf-8');

// Ajouter en fin de fichier
await fs.appendFile('log.txt', `${new Date().toISOString()} — action\n`);

// Lire un JSON
async function lireJson(fichier) {
  const brut = await fs.readFile(fichier, 'utf-8');
  return JSON.parse(brut);
}

// Écrire un JSON
async function ecrireJson(fichier, data) {
  await fs.writeFile(fichier, JSON.stringify(data, null, 2));
}

// Chemin absolu — indépendant du répertoire courant
const chemin = path.join(__dirname, 'data', 'users.json');

// Vérifier l'existence
try {
  await fs.access(chemin);
  console.log('existe');
} catch { console.log('inexistant'); }

// Lister un dossier
const fichiers = await fs.readdir('./uploads');

// Créer un dossier
await fs.mkdir('./uploads', { recursive: true }); // recursive = pas d'erreur si existe

// Supprimer
await fs.unlink('./temp/fichier.tmp');
⚠️

Toujours utiliser fs/promises (ou util.promisify) plutôt que fs synchrone (readFileSync) dans un serveur. Les méthodes sync bloquent l'event loop et empêchent le serveur de traiter d'autres requêtes pendant la lecture.

Cas pratique — upload de fichier avec multer
// npm install multer
const multer = require('multer');

const stockage = multer.diskStorage({
  destination: './uploads/',
  filename: (req, file, cb) => {
    const ext  = path.extname(file.originalname);
    const nom  = Date.now() + ext;
    cb(null, nom);
  },
});

const upload = multer({
  storage: stockage,
  limits: { fileSize: 5 * 1024 * 1024 },   // 5 MB max
  fileFilter: (req, file, cb) => {
    const ok = ['image/jpeg', 'image/png'].includes(file.mimetype);
    cb(null, ok);
  },
});

app.post('/upload', upload.single('photo'), (req, res) => {
  res.json({ url: `/uploads/${req.file.filename}` });
});

Variables d'environnement & .env

.env — stocker les secrets
# .env — NE JAMAIS committer ce fichier !
# L'ajouter dans .gitignore

PORT=3000
NODE_ENV=development

# Base de données
DB_HOST=localhost
DB_PORT=3306
DB_NAME=mon_app
DB_USER=root
DB_PASSWORD=mot_de_passe_secret

# JWT
JWT_SECRET=une_chaine_tres_longue_et_aleatoire_32chars
JWT_EXPIRES_IN=7d

# API externe
OPENWEATHER_API_KEY=abc123xyz
.env.example — ce qu'on committe à la place
# .env.example — modèle sans les vraies valeurs
PORT=3000
NODE_ENV=development
DB_HOST=localhost
DB_PORT=3306
DB_NAME=
DB_USER=
DB_PASSWORD=
JWT_SECRET=
JWT_EXPIRES_IN=7d
Utiliser dotenv dans le code
// npm install dotenv
// TOUJOURS charger en premier dans index.js
require('dotenv').config();

// Accéder aux variables
const port     = process.env.PORT     || 3000;
const dbHost   = process.env.DB_HOST;
const jwtSecret = process.env.JWT_SECRET;
const isDev    = process.env.NODE_ENV === 'development';

// Bonne pratique : centraliser dans config/
// config/index.js
module.exports = {
  port:     process.env.PORT || 3000,
  db: {
    host:     process.env.DB_HOST,
    port:     Number(process.env.DB_PORT) || 3306,
    name:     process.env.DB_NAME,
    user:     process.env.DB_USER,
    password: process.env.DB_PASSWORD,
  },
  jwt: {
    secret:    process.env.JWT_SECRET,
    expiresIn: process.env.JWT_EXPIRES_IN || '7d',
  },
};

// Utilisation dans le code
const config = require('./config');
app.listen(config.port);
💡

En production, ne pas déployer le fichier .env — configurer les variables directement dans l'environnement du serveur (PM2 ecosystem, variables d'environnement système, secrets du service cloud).

Connexion base de données — MySQL & MongoDB

MySQL avec mysql2 — pool de connexions
// npm install mysql2
// config/db.js
const mysql = require('mysql2/promise');

const pool = mysql.createPool({
  host:     process.env.DB_HOST,
  port:     process.env.DB_PORT,
  database: process.env.DB_NAME,
  user:     process.env.DB_USER,
  password: process.env.DB_PASSWORD,
  waitForConnections: true,
  connectionLimit: 10,         // max 10 connexions simultanées
  queueLimit: 0,
});

module.exports = pool;

// models/Article.js
const db = require('../config/db');

const Article = {
  async findAll() {
    const [rows] = await db.query('SELECT * FROM articles');
    return rows;
  },

  async findById(id) {
    const [rows] = await db.query(
      'SELECT * FROM articles WHERE id = ?', [id]
    );
    return rows[0] || null;
  },

  async create({ titre, contenu, auteur_id }) {
    const [result] = await db.query(
      'INSERT INTO articles (titre, contenu, auteur_id) VALUES (?, ?, ?)',
      [titre, contenu, auteur_id]    // ← paramètres préparés = protection SQL injection
    );
    return { id: result.insertId, titre, contenu, auteur_id };
  },

  async update(id, data) {
    await db.query(
      'UPDATE articles SET titre=?, contenu=? WHERE id=?',
      [data.titre, data.contenu, id]
    );
  },

  async delete(id) {
    await db.query('DELETE FROM articles WHERE id=?', [id]);
  },
};

module.exports = Article;
MongoDB avec Mongoose
// npm install mongoose
// config/db.js
const mongoose = require('mongoose');

async function connecterBDD() {
  await mongoose.connect(process.env.MONGO_URI);
  console.log('MongoDB connecté');
}
module.exports = connecterBDD;

// Dans index.js
await connecterBDD();

// models/Article.js — schéma Mongoose
const { Schema, model } = require('mongoose');

const articleSchema = new Schema({
  titre:    { type: String, required: true, trim: true },
  contenu:  { type: String, required: true },
  auteur:   { type: Schema.Types.ObjectId, ref: 'User' },
  publie:   { type: Boolean, default: false },
  tags:     [String],
}, { timestamps: true });  // createdAt, updatedAt auto

module.exports = model('Article', articleSchema);

// CRUD avec Mongoose
const Article = require('./models/Article');

const tous   = await Article.find();
const un     = await Article.findById(id);
const filtre = await Article.find({ publie: true }).sort('-createdAt').limit(10);
const cree   = await Article.create({ titre, contenu });
await Article.findByIdAndUpdate(id, { titre: 'Nouveau' });
await Article.findByIdAndDelete(id);
MySQLMongoDB / MongooseQuand choisir
Tables & colonnesCollections & documentsMySQL si données relationnelles
Schéma fixeSchéma flexibleMongoDB si structure variable
SQL (JOIN, etc.)Aggregation pipelineMySQL si beaucoup de jointures
mysql2mongooseMongoDB si démo/prototype

Authentification JWT

Un JSON Web Token est un jeton signé que le serveur délivre à la connexion. Le client le stocke et l'envoie dans chaque requête. Le serveur vérifie la signature — sans consulter la base de données.

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 . eyJ1c2VySWQiOjEsInJvbGUiOiJhZG1pbiIsImlhdCI6MTcxOH0 . SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c Header . Payload . Signature
Connexion — générer un token
// npm install jsonwebtoken bcryptjs
const jwt     = require('jsonwebtoken');
const bcrypt  = require('bcryptjs');

// Inscription — hacher le mot de passe
exports.inscription = async (req, res, next) => {
  try {
    const { nom, email, motDePasse } = req.body;
    const hash = await bcrypt.hash(motDePasse, 12); // coût = 12
    const user = await User.create({ nom, email, motDePasse: hash });
    res.status(201).json({ message: 'Compte créé', id: user.id });
  } catch (err) { next(err); }
};

// Connexion — vérifier et signer
exports.connexion = async (req, res, next) => {
  try {
    const { email, motDePasse } = req.body;
    const user = await User.findByEmail(email);
    if (!user) return res.status(401).json({ erreur: 'Identifiants incorrects' });

    const ok = await bcrypt.compare(motDePasse, user.motDePasse);
    if (!ok) return res.status(401).json({ erreur: 'Identifiants incorrects' });

    const token = jwt.sign(
      { userId: user.id, role: user.role },   // payload (visible !)
      process.env.JWT_SECRET,                  // clé secrète
      { expiresIn: process.env.JWT_EXPIRES_IN || '7d' }
    );

    res.json({ token, user: { id: user.id, nom: user.nom, email: user.email } });
  } catch (err) { next(err); }
};
Middleware — vérifier le token sur les routes protégées
// middleware/auth.js
const jwt = require('jsonwebtoken');

function verifierToken(req, res, next) {
  const authHeader = req.headers['authorization'];
  // Header attendu : "Authorization: Bearer eyJhbGci..."
  if (!authHeader?.startsWith('Bearer '))
    return res.status(401).json({ erreur: 'Token manquant' });

  const token = authHeader.slice(7);
  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded;   // { userId, role, iat, exp }
    next();
  } catch (err) {
    res.status(401).json({ erreur: 'Token invalide ou expiré' });
  }
}

// Middleware de rôle
function exigerRole(role) {
  return (req, res, next) => {
    if (req.user?.role !== role)
      return res.status(403).json({ erreur: 'Accès interdit' });
    next();
  };
}

module.exports = { verifierToken, exigerRole };

// Utilisation dans les routes
const { verifierToken, exigerRole } = require('../middleware/auth');

router.get('/profil', verifierToken, voirProfil);
router.delete('/:id', verifierToken, exigerRole('admin'), supprimer);
⚠️

Le payload JWT est encodé en base64, pas chiffré — ne jamais y mettre de mot de passe ou de donnée sensible. Seule la signature garantit l'intégrité.

Déploiement & PM2

PM2 — gestionnaire de processus Node
# Installer PM2 globalement
npm install -g pm2

# Lancer l'application
pm2 start src/index.js --name mon-api

# Lancer avec rechargement auto (équiv. nodemon en prod)
pm2 start src/index.js --name mon-api --watch

# Voir les applications en cours
pm2 list

# Logs en temps réel
pm2 logs mon-api

# Redémarrer
pm2 restart mon-api

# Arrêter
pm2 stop mon-api

# Démarrer au boot du serveur
pm2 startup
pm2 save
ecosystem.config.js — configuration PM2
module.exports = {
  apps: [{
    name:        'mon-api',
    script:      'src/index.js',
    instances:   'max',        // utiliser tous les CPU
    exec_mode:   'cluster',    // répartition de charge
    watch:       false,         // pas de watch en prod
    env: {
      NODE_ENV: 'production',
      PORT:     3000,
    },
  }],
};

# Lancer avec ce fichier
pm2 start ecosystem.config.js
Checklist de déploiement
# 1. Variables d'environnement sur le serveur
export NODE_ENV=production
export JWT_SECRET=...
export DB_PASSWORD=...

# 2. Installer seulement les dépendances de prod
npm install --production

# 3. Ne jamais exposer Node directement sur le port 80
#    → Utiliser un reverse proxy (Nginx)

# nginx.conf
server {
    listen 80;
    server_name mon-api.com;

    location / {
        proxy_pass http://localhost:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

# 4. HTTPS avec Let's Encrypt
sudo certbot --nginx -d mon-api.com

# 5. Vérifier que l'app redémarre après reboot
pm2 startup   # génère la commande à exécuter
pm2 save      # sauvegarde la liste des apps
💡

NODE_ENV=production active automatiquement les optimisations dans Express (cache des vues, messages d'erreur simplifiés, etc.) et dans la plupart des bibliothèques npm.

Intégration avec React & Vue

Vue 3 — appels API avec fetch
// src/services/api.js — centraliser les appels API
const BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:3000/api';

async function requete(url, options = {}) {
  const token = localStorage.getItem('token');
  const res = await fetch(BASE_URL + url, {
    headers: {
      'Content-Type': 'application/json',
      ...(token && { Authorization: `Bearer ${token}` }),
    },
    ...options,
  });
  if (!res.ok) throw new Error(`Erreur ${res.status}`);
  return res.json();
}

export const api = {
  get:    (url)         => requete(url),
  post:   (url, data)   => requete(url, { method: 'POST',   body: JSON.stringify(data) }),
  put:    (url, data)   => requete(url, { method: 'PUT',    body: JSON.stringify(data) }),
  delete: (url)         => requete(url, { method: 'DELETE' }),
};

// Composant Vue — utiliser l'API
// <script setup>
import { ref, onMounted } from 'vue';
import { api } from '../services/api';

const articles = ref([]);
const chargement = ref(true);
const erreur = ref(null);

onMounted(async () => {
  try {
    articles.value = await api.get('/articles');
  } catch (e) { erreur.value = e.message; }
  finally      { chargement.value = false; }
});
Proxy Vite — éviter les problèmes CORS en dev
// vite.config.js — proxy en développement
import { defineConfig } from 'vite';
import vue             from '@vitejs/plugin-vue';

export default defineConfig({
  plugins: [vue()],
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:3000',  // ton API Express
        changeOrigin: true,
      },
    },
  },
});
// Avec ce proxy, fetch('/api/articles') en dev passe par
// le serveur Vite → redirige vers Express → pas de CORS
Structure monorepo full-stack
projet-fullstack/
├── backend/          ← API Express
│   ├── src/
│   │   └── index.js
│   └── package.json
├── frontend/         ← Vite + Vue/React
│   ├── src/
│   │   └── App.vue
│   └── package.json
└── README.md

# Lancer les deux en parallèle (package racine)
# npm install concurrently --save-dev
# "dev": "concurrently \"npm run dev -w backend\" \"npm run dev -w frontend\""
🔗

En production, le front est buildé (npm run build → dossier dist/). Express peut servir ces fichiers statiques directement, ou les deux sont déployés séparément (front sur Netlify/Vercel, back sur VPS).

Cheat Sheet Node.js & Express

⌨️ Commandes

node fichier.jsExécuter un script
npm init -yNouveau projet
npm install XInstaller un package
npm run devMode dev (nodemon)
npm startMode production
pm2 start app.jsDéploiement PM2
pm2 logsLogs en direct

🛣️ Express routing

app.get(url, fn)GET lire
app.post(url, fn)POST créer
app.put(url, fn)PUT remplacer
app.patch(url, fn)PATCH modifier
app.delete(url, fn)DELETE supprimer
req.params.id:id dans l'URL
req.query.page?page=2
req.bodyBody JSON (express.json())

📦 Packages essentiels

expressServeur web
dotenvVariables .env
corsCross-Origin
mysql2MySQL
mongooseMongoDB ODM
jsonwebtokenJWT
bcryptjsHachage mots de passe
multerUpload fichiers
nodemonReload auto (dev)

🔐 JWT flow

Connexionjwt.sign(payload, secret)
Vérifierjwt.verify(token, secret)
HeaderAuthorization: Bearer TOKEN
Hacher mdpbcrypt.hash(mdp, 12)
Comparerbcrypt.compare(mdp, hash)
401Pas de token / expiré
403Token ok, pas les droits