Développement Web Full Stack

Progression
NB3BN Info - Centre de Formation Informatique au Maroc
Développement Web Full Stack

Manuel de formation complet - 12 sections - Formation théorique et pratique

12
Sections
2
Niveau
50+
Pages
2024
Édition
Commencer la Formation
Section 1

Introduction au Développement Web & Écosystème

Qu'est-ce que le Développement Web ?

Le développement web consiste à concevoir, coder et déployer des applications accessibles depuis un navigateur internet. C'est l'une des compétences les plus demandées au monde, portée par la transformation numérique de toutes les industries.

Les 3 couches du Web :
Front-end — Ce que l'utilisateur voit et avec quoi il interagit (HTML, CSS, JavaScript)
Back-end — La logique métier, les serveurs, les bases de données (Node.js, Python, PHP)
Full Stack — Maîtrise complète des deux couches

L'évolution du Web

ÈrePériodeCaractéristiques
Web 1.01991–2000Pages statiques, lecture seule, HTML simple
Web 2.02000–2010Sites dynamiques, réseaux sociaux, AJAX, Flash
Web 3.02010–2020Applications mobiles, APIs REST, frameworks JS
Web 4.02020+IA intégrée, PWA, WebAssembly, edge computing

La Stack Technologique Complète

Un développeur Full Stack doit maîtriser plusieurs couches technologiques :

1
Client (Navigateur) : HTML5, CSS3, JavaScript ES6+, frameworks (React, Vue, Angular)
2
Serveur (Backend) : Node.js/Express, APIs REST, authentification, cache
3
Base de données : MySQL, PostgreSQL, SQLite, MongoDB, Redis
4
Infrastructure : Git, déploiement cloud, DNS, HTTPS, CDN

Outils du Développeur Web

OutilUsageRecommandation
VS CodeÉditeur de code★★★★★ — Standard industrie
Chrome DevToolsDebug navigateur★★★★★ — Indispensable
PostmanTester les APIs★★★★☆ — Très utile
GitVersioning★★★★★ — Obligatoire
TerminalLigne de commande★★★★★ — Obligatoire
FigmaMaquettes UI/UX★★★★☆ — Recommandé

Le Cycle de Développement d'un Projet Web

Tout projet web suit un processus structuré :

  1. Analyse des besoins — Réunions client, définition des fonctionnalités
  2. Conception (Design) — Wireframes, maquettes, charte graphique
  3. Architecture technique — Choix du stack, schéma de base de données
  4. Développement Front-end — HTML/CSS/JS, intégration du design
  5. Développement Back-end — APIs, logique métier, BDD
  6. Tests — Tests unitaires, tests d'intégration, tests utilisateurs
  7. Déploiement — Mise en production, configuration serveur
  8. Maintenance — Corrections, mises à jour, nouvelles fonctionnalités
Conseil : Commencez par maîtriser HTML, CSS et JavaScript avant d'apprendre un framework. Les bases sont indispensables !

Question : Quelle technologie est responsable de l'interactivité d'une page web ?

A) HTML
B) CSS
C) JavaScript ✓
D) SQL
Section 2

HTML5 — Structure Sémantique Complète

HTML5 : Le Squelette de Toute Page Web

HTML (HyperText Markup Language) version 5 est le standard actuel pour structurer le contenu du web. Il introduit de nouvelles balises sémantiques qui améliorent l'accessibilité et le référencement naturel (SEO).

Structure de Base d'une Page HTML5

<!DOCTYPE html>
<html lang="fr">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta name="description" content="Description pour le SEO">
  <title>Ma Page Web</title>
  <link rel="stylesheet" href="styles.css">
  <link rel="icon" href="favicon.ico">
</head>
<body>
  <header>
    <nav>...Navigation...</nav>
  </header>
  <main>
    <section>...Contenu principal...</section>
    <aside>...Barre latérale...</aside>
  </main>
  <footer>...Pied de page...</footer>
  <script src="app.js"></script>
</body>
</html>

Les Balises Sémantiques HTML5

<header> En-tête de la page ou d'une section — logo, titre, navigation principale
<nav> Bloc de navigation — liens principaux, menus, breadcrumbs
<main> Contenu principal unique de la page — une seule balise <main> par page
<section> Section thématique de contenu avec son propre titre
<article> Contenu autonome : billet de blog, actualité, commentaire
<aside> Contenu secondaire lié mais non essentiel : sidebar, publicité
<footer> Pied de page — copyright, liens légaux, contacts
<figure> / <figcaption> Image ou médias avec leur légende

Formulaires HTML5 Avancés

HTML5 introduit de nouveaux types d'input et attributs de validation :

<form action="/api/inscription" method="POST">

  <!-- Champ email avec validation auto -->
  <input type="email" name="email" required placeholder="votre@email.com">

  <!-- Mot de passe avec longueur minimale -->
  <input type="password" name="mdp" minlength="8" required>

  <!-- Sélection de date -->
  <input type="date" name="naissance" min="1950-01-01">

  <!-- Curseur de valeur -->
  <input type="range" name="niveau" min="1" max="10" value="5">

  <!-- Sélection de couleur -->
  <input type="color" name="couleur" value="#2563eb">

  <!-- Téléchargement de fichier -->
  <input type="file" name="photo" accept="image/*">

  <!-- Zone de texte multiligne -->
  <textarea name="message" rows="5" maxlength="500"></textarea>

  <!-- Liste déroulante -->
  <select name="ville">
    <option value="">Choisir une ville</option>
    <option value="casa">Casablanca</option>
    <option value="rabat">Rabat</option>
  </select>

  <button type="submit">S'inscrire</button>
</form>

Tableaux HTML Structurés

<table>
  <caption>Résultats des étudiants</caption>
  <thead>
    <tr>
      <th scope="col">Nom</th>
      <th scope="col">Note</th>
      <th scope="col">Mention</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Ahmed Ben Ali</td>
      <td>17.5/20</td>
      <td>Très Bien</td>
    </tr>
  </tbody>
  <tfoot>
    <tr><td colspan="3">Résultats de l'examen final 2024</td></tr>
  </tfoot>
</table>

Multimédia HTML5 : Audio & Vidéo

<!-- Vidéo avec plusieurs formats -->
<video controls width="800" poster="thumbnail.jpg">
  <source src="video.mp4" type="video/mp4">
  <source src="video.webm" type="video/webm">
  Votre navigateur ne supporte pas la vidéo HTML5.
</video>

<!-- Audio avec contrôles -->
<audio controls>
  <source src="audio.mp3" type="audio/mpeg">
  <source src="audio.ogg" type="audio/ogg">
</audio>

<!-- Canvas pour le dessin 2D -->
<canvas id="monCanvas" width="500" height="300"></canvas>

Accessibilité et ARIA

L'accessibilité (a11y) est essentielle pour que tous les utilisateurs, y compris les personnes handicapées, puissent utiliser votre site :

<!-- Attributs ARIA -->
<button aria-label="Fermer la fenêtre" aria-expanded="false">×</button>
<nav aria-label="Navigation principale">...</nav>
<img src="logo.png" alt="Logo de NB3BN Info — Centre de formation">
<input id="email" type="email">
<label for="email">Adresse email :</label> <!-- for = id de l'input -->
Important : Toujours ajouter un attribut alt descriptif sur les images pour le SEO et l'accessibilité.

Question : Quelle balise HTML5 représente le contenu principal unique d'une page ?

<section>
<main> ✓
<article>
<div>
Section 3

CSS3 — Mise en Forme, Flexbox et Grid

CSS3 : Maîtriser le Style et la Mise en Page

CSS (Cascading Style Sheets) version 3 contrôle l'apparence visuelle de chaque élément HTML. Il permet de créer des designs modernes, animés et réactifs.

Les Sélecteurs CSS

/* ── Sélecteurs de base ── */
p          { color: #333; }           /* Toutes les balises <p> */
.card      { border-radius: 8px; }   /* Classe .card */
#header    { position: sticky; }     /* ID unique */
a:hover    { color: #2563eb; }       /* Pseudo-classe */
p::first-line { font-weight: bold; } /* Pseudo-élément */

/* ── Sélecteurs avancés ── */
ul > li           { list-style: disc; }  /* Enfant direct */
.nav a            { text-decoration: none; } /* Descendant */
input[type="text"] { border: 1px solid; }   /* Attribut */
section:nth-child(2) { background: #f0f9ff; } /* Nth-child */
p:not(.special)  { color: gray; }        /* Négation */

Le Modèle de Boîte (Box Model)

De l'intérieur vers l'extérieur :
Content → Padding (espace intérieur) → Border (bordure) → Margin (espace extérieur)
.boite {
  /* Contenu */
  width: 300px;
  height: 200px;

  /* Espace intérieur */
  padding: 20px;           /* tous les côtés */
  padding: 10px 20px;      /* vertical | horizontal */
  padding: 10px 15px 20px 25px; /* haut | droite | bas | gauche */

  /* Bordure */
  border: 2px solid #2563eb;
  border-radius: 12px;

  /* Espace extérieur */
  margin: 0 auto;  /* centrer horizontalement */

  /* box-sizing recommandé */
  box-sizing: border-box; /* padding inclus dans width/height */
}

Flexbox — Mise en Page Unidimensionnelle

Flexbox est idéal pour aligner des éléments sur une ligne ou une colonne :

/* ── Conteneur Flex ── */
.container {
  display: flex;
  flex-direction: row;          /* row | column | row-reverse */
  justify-content: space-between; /* axe principal */
  align-items: center;          /* axe croisé */
  flex-wrap: wrap;              /* retour à la ligne si nécessaire */
  gap: 16px;                    /* espacement entre éléments */
}

/* ── Éléments Flex ── */
.item {
  flex: 1;          /* grandir équitablement */
  flex: 0 0 250px;  /* taille fixe, pas de croissance */
  align-self: flex-start; /* alignement individuel */
  order: 2;         /* ordre d'affichage */
}

CSS Grid — Mise en Page Bidimensionnelle

CSS Grid permet de créer des mises en page complexes en lignes ET colonnes :

/* ── Grille de base ── */
.grille {
  display: grid;
  grid-template-columns: repeat(3, 1fr);  /* 3 colonnes égales */
  grid-template-rows: auto;
  gap: 24px;
}

/* ── Grille avancée avec zones nommées ── */
.layout {
  display: grid;
  grid-template-columns: 250px 1fr;
  grid-template-rows: 80px 1fr 60px;
  grid-template-areas:
    "header  header"
    "sidebar main"
    "footer  footer";
  min-height: 100vh;
}
.header  { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main    { grid-area: main; }
.footer  { grid-area: footer; }

/* ── Responsive avec auto-fit ── */
.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 20px;
}

Variables CSS (Custom Properties)

/* ── Définition dans :root ── */
:root {
  --primary:     #2563eb;
  --secondary:   #0ea5e9;
  --text:        #1e293b;
  --bg:          #f8fafc;
  --radius:      12px;
  --shadow:      0 4px 20px rgba(0,0,0,0.1);
  --transition:  all 0.3s ease;
}

/* ── Utilisation ── */
.btn {
  background: var(--primary);
  border-radius: var(--radius);
  transition: var(--transition);
  box-shadow: var(--shadow);
}

/* ── Modification dynamique avec JS ── */
document.documentElement.style.setProperty('--primary', '#dc2626');

Animations et Transitions CSS

/* ── Transition simple ── */
.btn {
  background: #2563eb;
  transition: background 0.3s ease, transform 0.2s;
}
.btn:hover {
  background: #1d4ed8;
  transform: translateY(-2px);
}

/* ── Animation @keyframes ── */
@keyframes fadeInUp {
  from { opacity: 0; transform: translateY(30px); }
  to   { opacity: 1; transform: translateY(0); }
}
.card {
  animation: fadeInUp 0.5s ease forwards;
}

/* ── Spinner de chargement ── */
@keyframes spin {
  to { transform: rotate(360deg); }
}
.spinner {
  width: 40px; height: 40px;
  border: 4px solid #e2e8f0;
  border-top-color: #2563eb;
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}
Conseil : Utilisez box-sizing: border-box globalement (*, *::before, *::after) pour éviter les surprises de calcul de largeur.

Question : Quelle propriété CSS centre un élément horizontalement dans un conteneur flex ?

align-items: center
justify-content: center ✓
text-align: center
margin: auto
Section 4

CSS Responsive Design & Bootstrap 5

Le Responsive Design : Un Site pour Tous les Écrans

Le Responsive Design (ou Design Adaptatif) fait en sorte qu'un site web s'affiche correctement sur tous les appareils : téléphones, tablettes, ordinateurs, téléviseurs.

Breakpoints standard (tailles d'écran) :
📱 Mobile : < 576px  |  📱 SM : 576px+  |  💻 MD : 768px+  |  💻 LG : 992px+  |  🖥 XL : 1200px+

Media Queries CSS

/* ── Mobile First (recommandé) ── */
/* Styles par défaut = mobile */
.container { padding: 16px; }
.grid      { grid-template-columns: 1fr; }

/* Tablette ≥ 768px */
@media (min-width: 768px) {
  .container { padding: 32px; max-width: 720px; margin: 0 auto; }
  .grid      { grid-template-columns: 1fr 1fr; }
}

/* Desktop ≥ 1024px */
@media (min-width: 1024px) {
  .container { max-width: 1200px; }
  .grid      { grid-template-columns: repeat(3, 1fr); }
}

/* ── Dark Mode ── */
@media (prefers-color-scheme: dark) {
  :root {
    --bg:   #0f172a;
    --text: #f1f5f9;
  }
}

/* ── Impression ── */
@media print {
  .nav, .ads, button { display: none; }
  body { font-size: 12pt; color: black; }
}

Bootstrap 5 — Le Framework CSS le plus Populaire

Bootstrap 5 est une bibliothèque CSS/JS qui accélère considérablement le développement d'interfaces. Elle fournit un système de grille, des composants prêts à l'emploi et des utilitaires CSS.

<!-- Inclure Bootstrap 5 via CDN -->
<link rel="stylesheet"
  href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
<script
  src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js">
</script>

Système de Grille Bootstrap

Bootstrap divise l'écran en 12 colonnes. Vous choisissez combien de colonnes chaque élément occupe selon la taille d'écran :

<div class="container">
  <div class="row g-4"> <!-- g-4 = gap entre colonnes -->

    <!-- Colonne pleine sur mobile, 1/2 sur tablette, 1/4 sur desktop -->
    <div class="col-12 col-md-6 col-lg-3">
      <div class="card h-100 shadow-sm">
        <div class="card-body">
          <h5 class="card-title">Formation</h5>
          <p class="card-text text-muted">Description...</p>
          <a href="#" class="btn btn-primary">En savoir plus</a>
        </div>
      </div>
    </div>

    <!-- Colonne 2/3 de la largeur -->
    <div class="col-md-8">...</div>
    <!-- Colonne 1/3 de la largeur -->
    <div class="col-md-4">...</div>

  </div>
</div>

Composants Bootstrap Essentiels

<!-- ── Navbar responsive ── -->
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
  <div class="container">
    <a class="navbar-brand fw-bold" href="/">NB3BN Info</a>
    <button class="navbar-toggler" data-bs-toggle="collapse" data-bs-target="#navMenu">
      <span class="navbar-toggler-icon"></span>
    </button>
    <div class="collapse navbar-collapse" id="navMenu">
      <ul class="navbar-nav ms-auto">
        <li class="nav-item"><a class="nav-link active" href="/">Accueil</a></li>
        <li class="nav-item"><a class="nav-link" href="/formations">Formations</a></li>
      </ul>
    </div>
  </div>
</nav>

<!-- ── Modal Bootstrap ── -->
<button class="btn btn-success" data-bs-toggle="modal" data-bs-target="#monModal">
  Ouvrir Modal
</button>
<div class="modal fade" id="monModal">
  <div class="modal-dialog modal-lg">
    <div class="modal-content">
      <div class="modal-header bg-primary text-white">
        <h5 class="modal-title">Titre</h5>
        <button class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
      </div>
      <div class="modal-body">Contenu...</div>
      <div class="modal-footer">
        <button class="btn btn-secondary" data-bs-dismiss="modal">Fermer</button>
        <button class="btn btn-primary">Confirmer</button>
      </div>
    </div>
  </div>
</div>

Classes Utilitaires Bootstrap

CatégorieExemplesEffet
Espacementm-3, p-4, mt-2, px-5margin/padding 0.25rem par unité
Couleurstext-primary, bg-dangerCouleurs thème Bootstrap
Flexd-flex, justify-content-betweenFlexbox rapide
Textefw-bold, fs-4, text-centerTypographie
Affichaged-none, d-md-blockMasquer/afficher par breakpoint
Borduresborder, rounded-3, shadowDécoration
Attention : Ne pas surcharger Bootstrap avec trop de CSS personnalisé. Utilisez les variables CSS Bootstrap (--bs-primary, etc.) pour personnaliser.

Question : Combien de colonnes compte le système de grille Bootstrap ?

8
10
12 ✓
16
Section 5

JavaScript Fondamentaux — Variables, Fonctions, DOM

JavaScript : Le Langage du Web

JavaScript (JS) est le seul langage de programmation natif des navigateurs. Il permet d'ajouter de l'interactivité, de gérer les données et de communiquer avec les serveurs.

Variables et Types de Données

// ── Déclarations ──
const NOM_SITE = "NB3BN Info";  // Constante — ne change jamais
let compteur = 0;                // Variable — peut changer
var ancien = "éviter";          // Ancien style — à éviter

// ── Types primitifs ──
const texte   = "Bonjour";          // string
const nombre  = 42;                  // number
const decimal = 3.14;                // number (float)
const vrai    = true;                // boolean
const nul     = null;                // null (valeur intentionnellement vide)
let nonDefini;                        // undefined
const grand   = 9007199254740993n;   // BigInt

// ── Vérification du type ──
console.log(typeof texte);   // "string"
console.log(typeof nombre);  // "number"
console.log(typeof vrai);    // "boolean"

// ── Conversion de types ──
const str  = String(42);        // "42"
const num  = Number("3.14");    // 3.14
const bool = Boolean(0);        // false (0, "", null = false)

Structures de Contrôle

// ── Conditions ──
const note = 15;
if (note >= 18)       { console.log("Excellent !"); }
else if (note >= 14)  { console.log("Bien"); }
else if (note >= 10)  { console.log("Passable"); }
else                  { console.log("Insuffisant"); }

// ── Opérateur ternaire ──
const mention = note >= 10 ? "Admis" : "Refusé";

// ── Switch ──
switch (mention) {
  case "Admis":  console.log("Félicitations !"); break;
  case "Refusé": console.log("Travaillez plus"); break;
  default:       console.log("Cas inconnu");
}

// ── Boucles ──
for (let i = 0; i < 5; i++) { console.log(i); }

const fruits = ["pomme", "banane", "orange"];
for (const fruit of fruits) { console.log(fruit); }

const personne = { nom: "Ali", age: 22 };
for (const cle in personne) { console.log(cle, personne[cle]); }

fruits.forEach((f, index) => console.log(index, f));

Fonctions JavaScript

// ── Déclaration classique ──
function additionner(a, b) {
  return a + b;
}

// ── Fonction fléchée (Arrow function) ──
const multiplier = (a, b) => a * b;
const saluer = nom => `Bonjour ${nom} !`;
const pi = () => 3.14159;

// ── Paramètres par défaut ──
function creerMessage(texte, auteur = "Anonyme", date = new Date()) {
  return `[${date.toLocaleDateString()}] ${auteur} : ${texte}`;
}

// ── Rest parameters ──
function somme(...nombres) {
  return nombres.reduce((acc, n) => acc + n, 0);
}
console.log(somme(1, 2, 3, 4, 5)); // 15

// ── Fonctions de haut niveau ──
const notes = [12, 15, 8, 17, 10];
const admis    = notes.filter(n => n >= 10);     // [12, 15, 17, 10]
const doubles  = notes.map(n => n * 2);           // [24, 30, 16, 34, 20]
const total    = notes.reduce((s, n) => s + n, 0); // 62
const meilleur = Math.max(...notes);               // 17

Manipulation du DOM (Document Object Model)

Le DOM est la représentation de la page HTML sous forme d'objet JavaScript. JS peut modifier, créer et supprimer des éléments :

// ── Sélectionner des éléments ──
const titre = document.getElementById("titre");
const bouton = document.querySelector(".btn-primary"); // CSS selector
const cards = document.querySelectorAll(".card");      // NodeList

// ── Modifier le contenu ──
titre.textContent = "Nouveau titre";     // Texte brut (sécurisé)
titre.innerHTML = "<strong>Titre</strong>"; // HTML (attention XSS !)

// ── Modifier les styles ──
bouton.style.backgroundColor = "#2563eb";
bouton.style.fontSize = "18px";

// ── Modifier les classes ──
bouton.classList.add("active");
bouton.classList.remove("disabled");
bouton.classList.toggle("visible");
const estActif = bouton.classList.contains("active");

// ── Modifier les attributs ──
const img = document.querySelector("img");
img.setAttribute("src", "nouvelle-image.jpg");
img.getAttribute("alt");  // Lire un attribut

// ── Créer et insérer des éléments ──
const p = document.createElement("p");
p.textContent = "Nouveau paragraphe";
p.className = "text-muted";
document.getElementById("container").appendChild(p);

// ── Supprimer un élément ──
const vieux = document.getElementById("ancien");
vieux.remove();  // ou vieux.parentNode.removeChild(vieux);

Événements JavaScript

// ── Événements de base ──
document.getElementById("btn").addEventListener("click", function(e) {
  e.preventDefault(); // Empêcher comportement par défaut (ex: submit de formulaire)
  console.log("Cliqué !");
});

// ── Délégation d'événements (recommandé pour listes dynamiques) ──
document.getElementById("liste").addEventListener("click", (e) => {
  if (e.target.matches(".item")) {
    console.log("Item cliqué :", e.target.textContent);
  }
});

// ── Événements de formulaire ──
const form = document.getElementById("monForm");
form.addEventListener("submit", (e) => {
  e.preventDefault();
  const data = new FormData(form);
  const email = data.get("email");
  const nom = data.get("nom");
  console.log({ email, nom });
});

// ── Événements clavier ──
document.addEventListener("keydown", (e) => {
  if (e.key === "Escape") fermerModal();
  if (e.ctrlKey && e.key === "s") sauvegarder();
});
Conseil : Évitez les gestionnaires d'événements inline (onclick="...") dans le HTML. Utilisez toujours addEventListener dans votre fichier JS.

Question : Que retourne notes.filter(n => n >= 10) pour [12, 8, 15, 5, 10] ?

[12, 15, 10]
[12, 15, 10] ✓
[8, 5]
[true, false, true, false, true]
Section 6

JavaScript Avancé — ES6+, Async, Fetch & Local Storage

JavaScript Moderne : ES6 et Au-delà

Les versions modernes de JavaScript (ES6/2015 et suivantes) introduisent des fonctionnalités puissantes qui simplifient et améliorent le code.

Destructuration et Spread Operator

// ── Destructuration d'objet ──
const utilisateur = { nom: "Sara", age: 28, ville: "Casablanca" };
const { nom, age, ville } = utilisateur;
const { nom: prenom, age: annees = 18 } = utilisateur; // Renommage + défaut

// ── Destructuration de tableau ──
const couleurs = ["rouge", "vert", "bleu"];
const [premiere, deuxieme] = couleurs;
const [, , troisieme] = couleurs; // Ignorer des éléments

// ── Spread operator ──
const tab1 = [1, 2, 3];
const tab2 = [4, 5, 6];
const fusion = [...tab1, ...tab2]; // [1, 2, 3, 4, 5, 6]

const obj1 = { a: 1, b: 2 };
const obj2 = { b: 3, c: 4 };
const merge = { ...obj1, ...obj2 }; // { a: 1, b: 3, c: 4 } — b écrasé

// ── Rest operator ──
const { nom: n, ...reste } = utilisateur;
console.log(reste); // { age: 28, ville: "Casablanca" }

Template Literals et Tagged Templates

// ── Template literals ──
const prenom = "Omar";
const score  = 18.5;
const message = `Félicitations ${prenom} ! Votre score est ${score}/20.`;

// ── Multilignes ──
const html = `
  <div class="card">
    <h2>${prenom}</h2>
    <p>Score : ${score.toFixed(1)}</p>
    <p class="${score >= 10 ? 'success' : 'danger'}">
      ${score >= 10 ? 'Admis' : 'Refusé'}
    </p>
  </div>
`;

Promises et Programmation Asynchrone

// ── Créer une Promise ──
function attendre(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

function chargerDonnees(url) {
  return new Promise((resolve, reject) => {
    fetch(url)
      .then(r => { if (!r.ok) reject("Erreur " + r.status); return r.json(); })
      .then(data => resolve(data))
      .catch(err => reject(err));
  });
}

// ── async / await (syntaxe moderne recommandée) ──
async function chargerFormations() {
  try {
    const response = await fetch('/api/formations');
    if (!response.ok) throw new Error(`HTTP ${response.status}`);
    const data = await response.json();
    return data.formations;
  } catch (erreur) {
    console.error("Erreur de chargement :", erreur.message);
    return [];
  }
}

// ── Promise.all — Parallèle ──
async function chargerTout() {
  const [formations, actualites, parametres] = await Promise.all([
    fetch('/api/formations').then(r => r.json()),
    fetch('/api/actualites').then(r => r.json()),
    fetch('/api/parametres').then(r => r.json()),
  ]);
  return { formations, actualites, parametres };
}

Fetch API — Communication avec le Serveur

// ── GET ──
const formation = await fetch('/api/formations/1').then(r => r.json());

// ── POST ──
const response = await fetch('/api/formations', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    titre: "Formation JavaScript",
    description: "Apprendre JS de zéro",
    prix: 1500
  })
});
const nouveau = await response.json();

// ── PUT (mise à jour) ──
await fetch('/api/formations/1', {
  method: 'PUT',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ prix: 1200 })
});

// ── DELETE ──
await fetch('/api/formations/1', { method: 'DELETE' });

// ── Upload de fichier ──
const formData = new FormData();
formData.append('photo', document.getElementById('inputFile').files[0]);
await fetch('/api/upload', { method: 'POST', body: formData });

Web Storage — Données Persistantes Côté Client

// ── localStorage (persiste après fermeture du navigateur) ──
localStorage.setItem('theme', 'dark');
const theme = localStorage.getItem('theme');      // "dark"
localStorage.removeItem('theme');
localStorage.clear(); // Supprimer tout

// ── Stocker des objets ──
const prefs = { langue: "ar", notifications: true, volume: 80 };
localStorage.setItem('preferences', JSON.stringify(prefs));
const prefsLues = JSON.parse(localStorage.getItem('preferences'));

// ── sessionStorage (effacé à la fermeture de l'onglet) ──
sessionStorage.setItem('token', 'eyJhbGciOiJIUzI1NiJ9...');

// ── Classe utilitaire ──
class Storage {
  static set(key, value) { localStorage.setItem(key, JSON.stringify(value)); }
  static get(key, defaut = null) {
    try { return JSON.parse(localStorage.getItem(key)) ?? defaut; }
    catch { return defaut; }
  }
  static del(key) { localStorage.removeItem(key); }
}

Modules JavaScript (ES Modules)

// ── helpers.js ──
export function formaterDate(date) {
  return new Intl.DateTimeFormat('fr-MA', {
    day: '2-digit', month: 'long', year: 'numeric'
  }).format(new Date(date));
}
export const COULEURS = { primary: '#2563eb', danger: '#dc2626' };
export default class API { /* ... */ }

// ── main.js ──
import API, { formaterDate, COULEURS } from './helpers.js';
import * as utils from './utils.js'; // Tout importer
Important : Toujours gérer les erreurs dans les appels fetch avec un bloc try/catch et vérifier response.ok avant de parser le JSON.

Question : Quelle méthode exécute plusieurs Promises en parallèle et attend toutes les réponses ?

Promise.race()
Promise.all() ✓
Promise.resolve()
Promise.any()
Section 7

Node.js & Express — Développement Backend JavaScript

Node.js : JavaScript côté Serveur

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

Pourquoi Node.js ?
✅ Un seul langage (JS) pour le front ET le back
✅ Architecture non-bloquante (event-driven) — gère des milliers de connexions
✅ NPM : le plus grand écosystème de packages au monde (2M+ packages)
✅ Excellente performance pour les APIs et les apps temps réel

Installation et Commandes de Base

# Installer Node.js (depuis nodejs.org)
# Vérifier l'installation
node --version   # v20.0.0
npm --version    # 10.0.0

# Exécuter un fichier JS
node app.js

# Initialiser un projet npm
npm init -y   # Crée package.json

# Installer des dépendances
npm install express        # Dépendance production
npm install -D nodemon     # Dépendance développement seulement

# Scripts dans package.json
npm start         # node app.js
npm run dev       # nodemon app.js (redémarrage auto)
npm run build     # Script de build

Créer un Serveur Express Complet

// app.js
const express = require('express');
const cors    = require('cors');
const path    = require('path');

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

// ── Middlewares globaux ──
app.use(cors());                                    // Autoriser les requêtes cross-origin
app.use(express.json());                            // Parser le JSON des requêtes
app.use(express.urlencoded({ extended: true }));    // Parser les formulaires HTML
app.use(express.static(path.join(__dirname, 'public'))); // Servir les fichiers statiques

// ── Middleware de logging ──
app.use((req, res, next) => {
  const ts = new Date().toISOString();
  console.log(`[${ts}] ${req.method} ${req.path}`);
  next(); // Passer au middleware suivant
});

// ── Routes ──
app.use('/api/formations', require('./routes/formations'));
app.use('/api/users',      require('./routes/users'));

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

// ── Gestion globale des erreurs ──
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).json({ error: 'Erreur serveur interne' });
});

app.listen(PORT, () => {
  console.log(`✅ Serveur démarré sur http://localhost:${PORT}`);
});

Structurer les Routes Express

// routes/formations.js
const express = require('express');
const router  = express.Router();
const db      = require('../database'); // Module BD

// GET /api/formations
router.get('/', async (req, res) => {
  try {
    const { page = 1, limit = 10, search = '' } = req.query;
    const offset = (page - 1) * limit;
    const formations = await db.query(
      'SELECT * FROM formations WHERE titre LIKE ? LIMIT ? OFFSET ?',
      [`%${search}%`, Number(limit), offset]
    );
    res.json({ success: true, data: formations, page: Number(page) });
  } catch (err) {
    res.status(500).json({ success: false, error: err.message });
  }
});

// GET /api/formations/:id
router.get('/:id', async (req, res) => {
  const formation = await db.query('SELECT * FROM formations WHERE id = ?', [req.params.id]);
  if (!formation.length) return res.status(404).json({ error: 'Non trouvé' });
  res.json({ success: true, data: formation[0] });
});

// POST /api/formations
router.post('/', async (req, res) => {
  const { titre, description, prix, duree } = req.body;
  if (!titre) return res.status(400).json({ error: 'Le titre est requis' });
  const result = await db.query(
    'INSERT INTO formations (titre, description, prix, duree) VALUES (?, ?, ?, ?)',
    [titre, description, prix, duree]
  );
  res.status(201).json({ success: true, id: result.insertId });
});

// PUT /api/formations/:id
router.put('/:id', async (req, res) => {
  const { titre, description, prix } = req.body;
  await db.query('UPDATE formations SET titre=?, description=?, prix=? WHERE id=?',
    [titre, description, prix, req.params.id]);
  res.json({ success: true });
});

// DELETE /api/formations/:id
router.delete('/:id', async (req, res) => {
  await db.query('DELETE FROM formations WHERE id = ?', [req.params.id]);
  res.json({ success: true });
});

module.exports = router;

Middleware Express — Authentification JWT

// middleware/auth.js
const jwt = require('jsonwebtoken');
const SECRET = process.env.JWT_SECRET || 'votre-secret-tres-long';

// Créer un token
function creerToken(payload) {
  return jwt.sign(payload, SECRET, { expiresIn: '24h' });
}

// Middleware de vérification
function verifierToken(req, res, next) {
  const authHeader = req.headers['authorization'];
  const token = authHeader?.split(' ')[1]; // "Bearer TOKEN"

  if (!token) return res.status(401).json({ error: 'Token manquant' });

  try {
    const decoded = jwt.verify(token, SECRET);
    req.user = decoded; // Ajouter l'utilisateur décodé à la requête
    next();
  } catch {
    res.status(403).json({ error: 'Token invalide ou expiré' });
  }
}

// Route de connexion
router.post('/login', async (req, res) => {
  const { email, password } = req.body;
  const user = await db.query('SELECT * FROM users WHERE email = ?', [email]);
  if (!user.length || !bcrypt.compareSync(password, user[0].password))
    return res.status(401).json({ error: 'Identifiants incorrects' });

  const token = creerToken({ id: user[0].id, email: user[0].email, role: user[0].role });
  res.json({ success: true, token });
});

// Route protégée
router.get('/profil', verifierToken, async (req, res) => {
  const user = await db.query('SELECT id, nom, email FROM users WHERE id = ?', [req.user.id]);
  res.json({ success: true, user: user[0] });
});
Conseil : Utilisez nodemon en développement pour que le serveur redémarre automatiquement à chaque modification du code : npx nodemon app.js

Question : Quel middleware Express est nécessaire pour lire le corps JSON d'une requête POST ?

express.text()
express.json() ✓
express.parse()
express.body()
Section 8

Bases de Données SQL — MySQL, PostgreSQL et SQLite

Les Bases de Données Relationnelles

Une base de données relationnelle organise les données sous forme de tables (comme des feuilles Excel) reliées entre elles par des clés. SQL (Structured Query Language) est le langage universel pour interagir avec ces bases.

SGBD populaires :
🐬 MySQL — Plus populaire, gratuit, sites web | 🐘 PostgreSQL — Plus avancé, open source
📦 SQLite — Fichier unique, idéal pour mobile/embedded | ☁️ Cloudflare D1 — SQLite distribué sur le edge

Créer et Gérer des Tables

-- ── Créer une table ──
CREATE TABLE IF NOT EXISTS formations (
  id          INTEGER PRIMARY KEY AUTOINCREMENT,
  titre       VARCHAR(200) NOT NULL,
  description TEXT,
  prix        DECIMAL(10,2) DEFAULT 0,
  duree       INTEGER,               -- en heures
  niveau      VARCHAR(50) CHECK (niveau IN ('débutant', 'intermédiaire', 'avancé')),
  image_url   TEXT,
  actif       BOOLEAN DEFAULT TRUE,
  created_at  DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at  DATETIME DEFAULT CURRENT_TIMESTAMP
);

-- ── Créer des index pour les performances ──
CREATE INDEX idx_formations_niveau ON formations(niveau);
CREATE INDEX idx_formations_prix   ON formations(prix);

-- ── Modifier une table ──
ALTER TABLE formations ADD COLUMN categorie VARCHAR(100);
ALTER TABLE formations RENAME COLUMN duree TO duree_heures;

-- ── Supprimer une table ──
DROP TABLE IF EXISTS formations;

Les Relations entre Tables

-- ── Table utilisateurs ──
CREATE TABLE users (
  id       INTEGER PRIMARY KEY AUTOINCREMENT,
  nom      VARCHAR(100) NOT NULL,
  email    VARCHAR(200) UNIQUE NOT NULL,
  password VARCHAR(255) NOT NULL,
  role     VARCHAR(20) DEFAULT 'etudiant'
);

-- ── Table inscriptions (relation Many-to-Many) ──
CREATE TABLE inscriptions (
  id           INTEGER PRIMARY KEY AUTOINCREMENT,
  user_id      INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
  formation_id INTEGER NOT NULL REFERENCES formations(id) ON DELETE CASCADE,
  statut       VARCHAR(20) DEFAULT 'en cours',
  note         DECIMAL(4,2),
  inscrit_le   DATETIME DEFAULT CURRENT_TIMESTAMP,
  UNIQUE(user_id, formation_id) -- Un user ne s'inscrit pas deux fois
);

Requêtes SQL Essentielles

-- ── INSERT ──
INSERT INTO formations (titre, description, prix, niveau) VALUES
  ('JavaScript Avancé', 'ES6, Async, DOM...', 1500, 'intermédiaire'),
  ('React.js', 'Framework frontend', 2000, 'avancé');

-- ── SELECT avec filtres ──
SELECT titre, prix, niveau
FROM formations
WHERE actif = 1 AND prix BETWEEN 500 AND 2000
ORDER BY prix ASC
LIMIT 10 OFFSET 0;

-- ── UPDATE ──
UPDATE formations
SET prix = prix * 0.9, updated_at = CURRENT_TIMESTAMP
WHERE niveau = 'débutant';

-- ── DELETE ──
DELETE FROM formations WHERE actif = 0 AND created_at < '2023-01-01';

Requêtes SQL Avancées — JOIN et Agrégats

-- ── INNER JOIN — correspondances dans les deux tables ──
SELECT u.nom, u.email, f.titre AS formation, i.note
FROM inscriptions i
INNER JOIN users u ON u.id = i.user_id
INNER JOIN formations f ON f.id = i.formation_id
WHERE i.statut = 'terminé'
ORDER BY i.note DESC;

-- ── LEFT JOIN — tous les utilisateurs, même sans inscription ──
SELECT u.nom, COUNT(i.id) AS nb_formations
FROM users u
LEFT JOIN inscriptions i ON i.user_id = u.id
GROUP BY u.id, u.nom
HAVING COUNT(i.id) > 0;

-- ── Agrégats ──
SELECT
  niveau,
  COUNT(*) AS nb_formations,
  AVG(prix) AS prix_moyen,
  MIN(prix) AS prix_min,
  MAX(prix) AS prix_max,
  SUM(prix) AS total_prix
FROM formations
WHERE actif = 1
GROUP BY niveau
ORDER BY prix_moyen DESC;

-- ── Sous-requête ──
SELECT nom, email FROM users
WHERE id IN (
  SELECT DISTINCT user_id FROM inscriptions
  WHERE formation_id = (SELECT id FROM formations WHERE titre = 'React.js')
);

Utiliser SQLite avec Node.js (better-sqlite3)

// Installation
// npm install better-sqlite3

const Database = require('better-sqlite3');
const db = new Database('ma_base.db');

// ── Créer la table ──
db.exec(`
  CREATE TABLE IF NOT EXISTS formations (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    titre TEXT NOT NULL,
    prix REAL DEFAULT 0
  )
`);

// ── Préparer et exécuter ──
const insert = db.prepare('INSERT INTO formations (titre, prix) VALUES (?, ?)');
const { lastInsertRowid } = insert.run('JavaScript', 1500);

// ── Lire des données ──
const getAll = db.prepare('SELECT * FROM formations WHERE prix <= ?');
const formations = getAll.all(2000); // Retourne un tableau

const getOne = db.prepare('SELECT * FROM formations WHERE id = ?');
const formation = getOne.get(1); // Retourne un objet ou undefined

// ── Transaction (atomique) ──
const insererPlusieurs = db.transaction((data) => {
  const stmt = db.prepare('INSERT INTO formations (titre, prix) VALUES (?, ?)');
  for (const f of data) stmt.run(f.titre, f.prix);
});
insererPlusieurs([{ titre: 'CSS', prix: 800 }, { titre: 'HTML', prix: 600 }]);

Bonnes Pratiques SQL

PratiqueMauvaisBon
Injections SQLquery("... WHERE id=" + id)prepare("... WHERE id=?").run(id)
SélectionSELECT *SELECT id, nom, email
PerformancePas d'indexIndex sur colonnes filtrées
IntégritéPas de contraintesNOT NULL, UNIQUE, FK
TransactionsOpérations séparéesRegrouper dans une transaction

Question : Quel type de JOIN retourne TOUTES les lignes de la table gauche, même sans correspondance ?

INNER JOIN
LEFT JOIN ✓
RIGHT JOIN
FULL JOIN
Section 9

APIs REST — Conception et Consommation

Les APIs REST : L'Épine Dorsale du Web Moderne

Une API REST (Representational State Transfer) est une interface qui permet à des applications de communiquer entre elles via HTTP. C'est le standard dominant pour la communication entre front-end et back-end.

Principes REST

Stateless Chaque requête est indépendante — le serveur ne garde pas de session entre les requêtes
Ressources Tout est une ressource identifiée par une URL : /api/formations, /api/users/42
Verbes HTTP GET (lire), POST (créer), PUT/PATCH (modifier), DELETE (supprimer)
Format JSON Standard d'échange de données : léger, lisible, universel
Codes HTTP 200 OK, 201 Créé, 400 Bad Request, 401 Non autorisé, 404 Non trouvé, 500 Erreur serveur

Convention de Nommage des URLs REST

ActionMéthodeURLDescription
ListerGET/api/formationsToutes les formations
DétailGET/api/formations/42Formation ID 42
CréerPOST/api/formationsNouvelle formation
RemplacerPUT/api/formations/42Mettre à jour complètement
ModifierPATCH/api/formations/42Modification partielle
SupprimerDELETE/api/formations/42Supprimer formation 42
ImbriquéeGET/api/formations/42/inscritsInscrits à la formation 42

Format de Réponse API Standardisé

// ── Réponse de succès pour liste ──
{
  "success": true,
  "data": [...],
  "meta": {
    "total": 150,
    "page": 1,
    "per_page": 10,
    "last_page": 15
  }
}

// ── Réponse de succès pour un élément ──
{
  "success": true,
  "data": {
    "id": 42,
    "titre": "JavaScript Avancé",
    "prix": 1500,
    "created_at": "2024-09-15T10:30:00Z"
  }
}

// ── Réponse d'erreur ──
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Le champ titre est requis",
    "fields": { "titre": "Ce champ est obligatoire" }
  }
}

Construire une API REST avec Hono (Cloudflare Workers)

// src/index.tsx — API avec Hono
import { Hono } from 'hono';
import { cors } from 'hono/cors';
import { validator } from 'hono/validator';

const app = new Hono<{ Bindings: { DB: D1Database } }>();
app.use('/api/*', cors());

// GET /api/formations?page=1&limit=10&search=js
app.get('/api/formations', async (c) => {
  const { page = '1', limit = '10', search = '' } = c.req.query();
  const offset = (Number(page) - 1) * Number(limit);

  const { results } = await c.env.DB.prepare(`
    SELECT * FROM formations
    WHERE actif = 1 AND (titre LIKE ? OR description LIKE ?)
    ORDER BY created_at DESC LIMIT ? OFFSET ?
  `).bind(`%${search}%`, `%${search}%`, Number(limit), offset).all();

  const { results: total } = await c.env.DB.prepare(
    'SELECT COUNT(*) as n FROM formations WHERE actif = 1'
  ).all();

  return c.json({ success: true, data: results, meta: { total: total[0].n, page } });
});

// POST /api/formations avec validation
app.post('/api/formations',
  validator('json', (value, c) => {
    if (!value.titre) return c.json({ error: 'titre requis' }, 400);
    return value;
  }),
  async (c) => {
    const { titre, description, prix = 0, niveau = 'débutant' } = await c.req.json();
    const { meta } = await c.env.DB.prepare(`
      INSERT INTO formations (titre, description, prix, niveau)
      VALUES (?, ?, ?, ?)
    `).bind(titre, description, prix, niveau).run();
    return c.json({ success: true, id: meta.last_row_id }, 201);
  }
);

Pagination, Filtrage et Tri

// Exemple de requête avec paramètres avancés
// GET /api/formations?page=2&limit=5&niveau=débutant&tri=prix&ordre=asc

app.get('/api/formations', async (c) => {
  const {
    page   = '1',
    limit  = '10',
    niveau = '',
    tri    = 'created_at',
    ordre  = 'desc'
  } = c.req.query();

  // Sécuriser les paramètres de tri
  const TRIS_AUTORISES = ['titre', 'prix', 'created_at'];
  const ORDRES_AUTORISES = ['asc', 'desc'];
  const triSec   = TRIS_AUTORISES.includes(tri) ? tri : 'created_at';
  const ordreSec = ORDRES_AUTORISES.includes(ordre) ? ordre : 'desc';

  let query = 'SELECT * FROM formations WHERE actif = 1';
  const params: any[] = [];
  if (niveau) { query += ' AND niveau = ?'; params.push(niveau); }
  query += ` ORDER BY ${triSec} ${ordreSec}`;
  query += ' LIMIT ? OFFSET ?';
  params.push(Number(limit), (Number(page) - 1) * Number(limit));

  const { results } = await c.env.DB.prepare(query).bind(...params).all();
  return c.json({ success: true, data: results });
});
Conseil : Versionnez votre API : /api/v1/formations. Cela permet de faire évoluer l'API sans casser les clients existants.

Question : Quel code HTTP retourner après une création réussie via POST ?

200 OK
201 Created ✓
204 No Content
202 Accepted
Section 10

Sécurité Web — XSS, CSRF, SQL Injection, HTTPS

La Sécurité Web : Une Priorité Absolue

Chaque application web est une cible potentielle. En 2024, les cyberattaques coûtent des milliards aux entreprises. Connaître les vulnérabilités et leurs solutions est indispensable pour tout développeur.

Règle d'or : Ne jamais faire confiance à aucune entrée utilisateur. Toujours valider, nettoyer et échapper les données côté serveur.

1. Injection SQL

L'attaque la plus dangereuse : injecter du SQL malveillant dans les entrées pour manipuler la base de données.

// ❌ VULNÉRABLE — Injection SQL possible
const query = "SELECT * FROM users WHERE email='" + email + "'";
// Si email = "' OR 1=1 --", retourne tous les utilisateurs !

// ✅ SÉCURISÉ — Requêtes préparées (Parameterized Queries)
const user = await db.prepare("SELECT * FROM users WHERE email = ?")
                     .bind(email).first();

// ✅ Validation supplémentaire
function validerEmail(email) {
  const regex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$/;
  if (!regex.test(email)) throw new Error("Email invalide");
  if (email.length > 254) throw new Error("Email trop long");
}

2. Cross-Site Scripting (XSS)

Injection de JavaScript malveillant dans les pages pour voler des cookies, rediriger des utilisateurs ou défigurer le site.

// ❌ VULNÉRABLE — innerHTML avec données utilisateur
element.innerHTML = userInput; // Danger !
document.write(req.query.search); // Danger !

// ✅ SÉCURISÉ — Utiliser textContent
element.textContent = userInput; // Échappe automatiquement le HTML

// ✅ Fonction d'échappement HTML
function escapeHTML(str) {
  return str
    .replace(/&/g, '&')
    .replace(//g, '>')
    .replace(/"/g, '"')
    .replace(/'/g, ''');
}

// ✅ Content Security Policy (CSP) — En-tête HTTP
// Dans Hono/Express :
app.use((req, res, next) => {
  res.setHeader('Content-Security-Policy',
    "default-src 'self'; script-src 'self' cdn.jsdelivr.net; style-src 'self' 'unsafe-inline'"
  );
  next();
});

3. CSRF (Cross-Site Request Forgery)

Forcer un utilisateur authentifié à exécuter des actions non désirées sur un autre site.

// ✅ Protection CSRF avec tokens
// Côté serveur — Générer un token unique par session
function genererCSRFToken() {
  return crypto.randomUUID(); // ou crypto.getRandomValues()
}

// Inclure dans chaque formulaire HTML
<form method="POST" action="/api/formations">
  <input type="hidden" name="_csrf" value="{{ csrfToken }}">
  ...
</form>

// Vérifier côté serveur
app.use('/api', (req, res, next) => {
  if (['POST','PUT','DELETE'].includes(req.method)) {
    const token = req.body._csrf || req.headers['x-csrf-token'];
    if (token !== req.session.csrfToken)
      return res.status(403).json({ error: 'CSRF token invalide' });
  }
  next();
});

// ✅ SameSite Cookie (protection moderne)
res.cookie('session', token, {
  httpOnly: true,   // Inaccessible par JS
  secure: true,     // HTTPS uniquement
  sameSite: 'Strict' // Bloque les requêtes cross-site
});

4. HTTPS et Chiffrement

// ✅ Forcer le HTTPS — Redirection HTTP → HTTPS
app.use((req, res, next) => {
  if (!req.secure && req.headers['x-forwarded-proto'] !== 'https') {
    return res.redirect(301, 'https://' + req.hostname + req.originalUrl);
  }
  next();
});

// ✅ En-têtes de sécurité HTTP essentiels
app.use((req, res, next) => {
  res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
  res.setHeader('X-Content-Type-Options', 'nosniff');
  res.setHeader('X-Frame-Options', 'SAMEORIGIN');
  res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
  next();
});

5. Authentification et Mots de Passe Sécurisés

// ✅ Hachage des mots de passe avec bcrypt
const bcrypt = require('bcryptjs');

// Inscription — hasher avant stockage
async function inscrire(email, mdp) {
  const hash = await bcrypt.hash(mdp, 12); // 12 rounds = coût CPU fort
  await db.run('INSERT INTO users (email, password) VALUES (?, ?)', [email, hash]);
}

// Connexion — comparer le hash
async function connecter(email, mdp) {
  const user = await db.get('SELECT * FROM users WHERE email = ?', [email]);
  if (!user) throw new Error('Utilisateur introuvable');
  const valide = await bcrypt.compare(mdp, user.password);
  if (!valide) throw new Error('Mot de passe incorrect');
  return user;
}

// ✅ Politique de mots de passe
function validerMotDePasse(mdp) {
  if (mdp.length < 8)    throw new Error('Minimum 8 caractères');
  if (!/[A-Z]/.test(mdp)) throw new Error('Au moins une majuscule');
  if (!/[0-9]/.test(mdp)) throw new Error('Au moins un chiffre');
}

6. Rate Limiting — Limiter les Tentatives

// npm install express-rate-limit
const rateLimit = require('express-rate-limit');

// Limiter les tentatives de connexion
const loginLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 5,                    // 5 tentatives maximum
  message: { error: 'Trop de tentatives. Réessayez dans 15 minutes.' },
  standardHeaders: true
});

app.post('/api/auth/login', loginLimiter, async (req, res) => {
  // ... logique de connexion
});

// Limiter l'API en général
const apiLimiter = rateLimit({ windowMs: 60000, max: 100 });
app.use('/api/', apiLimiter);

Checklist de Sécurité

VulnérabilitéSolutionPriorité
SQL InjectionRequêtes préparées🔴 Critique
XSSÉchappement HTML, CSP🔴 Critique
Mots de passebcrypt, min 12 rounds🔴 Critique
HTTPSCertificat SSL, HSTS🔴 Critique
CSRFTokens CSRF, SameSite🟠 Élevé
Rate Limitingexpress-rate-limit🟠 Élevé
Dépendancesnpm audit régulier🟡 Moyen

Question : Quelle technique protège contre les injections SQL ?

Validation de la longueur uniquement
Requêtes préparées (parameterized queries) ✓
Convertir en majuscules
Limiter à 50 caractères
Section 11

Git & GitHub — Versioning Professionnel

Git : Système de Contrôle de Version

Git est un outil indispensable pour tout développeur. Il permet de suivre l'historique des modifications, de collaborer en équipe et de revenir à n'importe quelle version passée du code.

Concepts clés de Git :
Repository (repo) — Dossier contenant tout le projet et son historique
Commit — Snapshot (photo) de l'état du code à un instant T
Branch — Ligne de développement parallèle
Merge — Fusion de deux branches
Remote — Dépôt distant (GitHub, GitLab, Bitbucket)

Configuration Initiale et Commandes de Base

# ── Configuration initiale (une seule fois) ──
git config --global user.name "Ahmed Ben Ali"
git config --global user.email "ahmed@example.com"
git config --global core.editor "code --wait"  # VS Code comme éditeur

# ── Initialiser un projet ──
git init                    # Nouveau repo local
git clone https://github.com/user/repo.git  # Cloner un repo existant

# ── Cycle de base ──
git status                  # Voir l'état des fichiers
git diff                    # Voir les modifications
git add fichier.js          # Ajouter un fichier spécifique
git add .                   # Ajouter tous les fichiers
git commit -m "feat: ajouter la page formations"
git log --oneline --graph   # Historique compact avec graphe
git show HEAD               # Détails du dernier commit

Gitignore — Exclure des Fichiers

# .gitignore — Ne JAMAIS committer :
node_modules/     # Dépendances npm
.env              # Variables d'environnement (CLÉS API !)
dist/             # Fichiers de build
.DS_Store         # Fichiers macOS
*.log             # Fichiers de logs
.wrangler/        # Cache Wrangler local
coverage/         # Rapports de tests
*.local           # Fichiers de config locaux

Gestion des Branches

# ── Créer et changer de branche ──
git branch feature/page-contact   # Créer une branche
git checkout feature/page-contact  # Aller sur la branche
git checkout -b fix/bug-login      # Créer + aller directement
git switch main                    # Syntaxe moderne (Git 2.23+)

# ── Voir les branches ──
git branch          # Branches locales
git branch -r       # Branches distantes
git branch -a       # Toutes les branches

# ── Fusionner ──
git checkout main
git merge feature/page-contact    # Fusionner la feature dans main

# ── Supprimer ──
git branch -d feature/page-contact  # Branche locale
git push origin --delete feature/page-contact  # Branche distante

Workflow Git Professionnel (GitFlow)

1
main — Branche principale, toujours stable, en production
2
develop — Branche d'intégration des nouvelles fonctionnalités
3
feature/xxx — Une branche par fonctionnalité, créée depuis develop
4
hotfix/xxx — Correction urgente en production, créée depuis main
5
release/v1.0 — Préparation d'une version, tests finaux avant main

Synchronisation avec GitHub

# ── Ajouter un remote (dépôt distant) ──
git remote add origin https://github.com/user/mon-projet.git
git remote -v    # Voir les remotes configurés

# ── Pousser du code ──
git push origin main                    # Pousser main
git push -u origin feature/ma-feature  # Pousser + définir l'upstream
git push --force-with-lease             # Force push sécurisé

# ── Récupérer du code ──
git fetch origin          # Télécharger sans merger
git pull origin main      # Fetch + merge automatique
git pull --rebase origin main  # Fetch + rebase (historique plus propre)

# ── Rebase interactif ──
git rebase -i HEAD~3   # Modifier les 3 derniers commits

Résoudre les Conflits de Merge

# Quand Git signale un conflit :
# <<<<<<< HEAD
#   Votre version
# =======
#   Leur version
# >>>>>>> feature/ma-feature

# 1. Ouvrir le fichier conflictuel dans VS Code
# 2. Choisir quelle version garder (ou combiner)
# 3. Supprimer les marqueurs <<<<<<<, =======, >>>>>>>
git add fichier-conflictuel.js
git commit -m "resolve: conflits de merge page contact"

Conventions de Messages de Commit

PréfixeUsageExemple
feat:Nouvelle fonctionnalitéfeat: ajouter la page formations
fix:Correction de bugfix: corriger l'affichage mobile
docs:Documentationdocs: mettre à jour le README
style:Formatage CSS/codestyle: corriger l'indentation
refactor:Refactorisationrefactor: simplifier la fonction auth
test:Teststest: ajouter tests pour l'API
chore:Maintenancechore: mettre à jour les dépendances

Question : Quelle commande Git crée une nouvelle branche ET y bascule immédiatement ?

git branch nouvelle
git checkout -b nouvelle ✓
git create nouvelle
git merge nouvelle
Section 12

Projet Final — Application Full Stack de A à Z

Projet : Plateforme de Gestion des Formations

Nous allons construire une application web complète : un site de gestion de formations avec inscription, administration et API REST. Ce projet intègre toutes les compétences du manuel.

Architecture du Projet

formations-app/
├── public/
│   ├── index.html         # Page d'accueil publique
│   ├── formations.html    # Liste des formations
│   ├── inscription.html   # Formulaire d'inscription
│   └── admin/
│       └── dashboard.html # Interface d'administration
├── src/
│   ├── index.js           # Serveur Express principal
│   ├── routes/
│   │   ├── formations.js  # CRUD formations
│   │   ├── users.js       # Gestion utilisateurs
│   │   └── auth.js        # Authentification
│   ├── middleware/
│   │   ├── auth.js        # Vérification JWT
│   │   └── validate.js    # Validation des données
│   └── database.js        # Connexion SQLite
├── migrations/
│   └── 001_schema.sql     # Schéma de la BDD
├── .env                   # Variables d'environnement
├── .gitignore
└── package.json

Étape 1 : Schéma de Base de Données

-- migrations/001_schema.sql
CREATE TABLE users (
  id       INTEGER PRIMARY KEY AUTOINCREMENT,
  nom      VARCHAR(100) NOT NULL,
  email    VARCHAR(200) UNIQUE NOT NULL,
  password VARCHAR(255) NOT NULL,
  role     VARCHAR(20) DEFAULT 'etudiant', -- etudiant | admin
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE formations (
  id          INTEGER PRIMARY KEY AUTOINCREMENT,
  titre       VARCHAR(200) NOT NULL,
  description TEXT,
  prix        DECIMAL(10,2) DEFAULT 0,
  duree       INTEGER,
  niveau      VARCHAR(50) DEFAULT 'débutant',
  image_url   TEXT,
  places_max  INTEGER DEFAULT 20,
  actif       BOOLEAN DEFAULT 1,
  created_at  DATETIME DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE inscriptions (
  id           INTEGER PRIMARY KEY AUTOINCREMENT,
  user_id      INTEGER REFERENCES users(id) ON DELETE CASCADE,
  formation_id INTEGER REFERENCES formations(id) ON DELETE CASCADE,
  statut       VARCHAR(20) DEFAULT 'en_attente',
  inscrit_le   DATETIME DEFAULT CURRENT_TIMESTAMP,
  UNIQUE(user_id, formation_id)
);

Étape 2 : API REST Complète

// src/index.js
const express = require('express');
const cors    = require('cors');
const jwt     = require('jsonwebtoken');
const bcrypt  = require('bcryptjs');
const db      = require('./database');

const app = express();
app.use(cors(), express.json(), express.static('public'));

// ── Auth ──
app.post('/api/auth/register', async (req, res) => {
  const { nom, email, password } = req.body;
  const hash = await bcrypt.hash(password, 12);
  const { lastID } = db.run(
    'INSERT INTO users (nom, email, password) VALUES (?,?,?)',
    [nom, email, hash]
  );
  res.status(201).json({ id: lastID, nom, email });
});

app.post('/api/auth/login', async (req, res) => {
  const { email, password } = req.body;
  const user = db.get('SELECT * FROM users WHERE email = ?', [email]);
  if (!user || !await bcrypt.compare(password, user.password))
    return res.status(401).json({ error: 'Identifiants incorrects' });
  const token = jwt.sign(
    { id: user.id, email: user.email, role: user.role },
    process.env.JWT_SECRET, { expiresIn: '24h' }
  );
  res.json({ token, user: { id: user.id, nom: user.nom, role: user.role } });
});

// ── Middleware Auth ──
const auth = (req, res, next) => {
  const token = req.headers.authorization?.split(' ')[1];
  try { req.user = jwt.verify(token, process.env.JWT_SECRET); next(); }
  catch { res.status(401).json({ error: 'Non autorisé' }); }
};

// ── CRUD Formations ──
app.get('/api/formations', (req, res) => {
  const { search = '', niveau = '' } = req.query;
  const formations = db.all(`
    SELECT f.*, COUNT(i.id) as inscrits
    FROM formations f LEFT JOIN inscriptions i ON i.formation_id = f.id
    WHERE f.actif = 1
    AND (f.titre LIKE ? OR f.description LIKE ?)
    ${niveau ? 'AND f.niveau = ?' : ''}
    GROUP BY f.id ORDER BY f.created_at DESC
  `, niveau ? [`%${search}%`, `%${search}%`, niveau] : [`%${search}%`, `%${search}%`]);
  res.json({ success: true, data: formations });
});

app.post('/api/formations', auth, (req, res) => {
  if (req.user.role !== 'admin') return res.status(403).json({ error: 'Admin requis' });
  const { titre, description, prix, duree, niveau, places_max } = req.body;
  const { lastID } = db.run(
    'INSERT INTO formations (titre,description,prix,duree,niveau,places_max) VALUES(?,?,?,?,?,?)',
    [titre, description, prix, duree, niveau, places_max]
  );
  res.status(201).json({ success: true, id: lastID });
});

// ── Inscription ──
app.post('/api/inscriptions', auth, (req, res) => {
  const { formation_id } = req.body;
  const formation = db.get('SELECT * FROM formations WHERE id = ?', [formation_id]);
  if (!formation) return res.status(404).json({ error: 'Formation introuvable' });

  const inscrits = db.get('SELECT COUNT(*) as n FROM inscriptions WHERE formation_id=?', [formation_id]);
  if (inscrits.n >= formation.places_max)
    return res.status(400).json({ error: 'Formation complète' });

  db.run('INSERT INTO inscriptions (user_id, formation_id) VALUES (?, ?)',
    [req.user.id, formation_id]);
  res.status(201).json({ success: true, message: 'Inscription réussie !' });
});

app.listen(3000, () => console.log('✅ Serveur : http://localhost:3000'));

Étape 3 : Interface Frontend

<!-- public/formations.html -->
<div class="container py-5">
  <div class="row g-3 mb-4">
    <div class="col-md-8">
      <input id="search" class="form-control form-control-lg"
             placeholder="Rechercher une formation...">
    </div>
    <div class="col-md-4">
      <select id="niveau" class="form-select form-select-lg">
        <option value="">Tous les niveaux</option>
        <option value="débutant">Débutant</option>
        <option value="intermédiaire">Intermédiaire</option>
        <option value="avancé">Avancé</option>
      </select>
    </div>
  </div>
  <div id="liste-formations" class="row g-4"></div>
</div>

<script>
async function charger() {
  const search = document.getElementById('search').value;
  const niveau = document.getElementById('niveau').value;
  const { data } = await fetch(`/api/formations?search=${search}&niveau=${niveau}`)
                          .then(r => r.json());
  document.getElementById('liste-formations').innerHTML = data.map(f => `
    <div class="col-md-4">
      <div class="card h-100 shadow-sm">
        <div class="card-body">
          <span class="badge bg-primary mb-2">${f.niveau}</span>
          <h5 class="card-title">${f.titre}</h5>
          <p class="text-muted">${f.description?.slice(0, 100)}...</p>
          <div class="d-flex justify-content-between align-items-center mt-3">
            <strong class="text-primary fs-5">${f.prix} MAD</strong>
            <button class="btn btn-sm btn-success" onclick="sInscrire(${f.id})">
              S'inscrire
            </button>
          </div>
        </div>
      </div>
    </div>
  `).join('');
}
document.getElementById('search').addEventListener('input', charger);
document.getElementById('niveau').addEventListener('change', charger);
charger();
</script>

Déploiement sur Cloudflare Pages

1
Adapter pour Hono : Réécrire l'API Express en Hono (Cloudflare Workers compatible)
2
D1 Database : Créer la base D1 : wrangler d1 create mon-app-production
3
Migrations : Appliquer le schéma : wrangler d1 migrations apply mon-app-production
4
Build : npm run build puis wrangler pages deploy dist
5
Variables d'env : wrangler secret put JWT_SECRET pour les secrets

Récapitulatif des Compétences Acquises

TechnologieMaîtriseUsage principal
HTML5⭐⭐⭐⭐⭐Structure sémantique, formulaires, accessibilité
CSS3 / Flexbox / Grid⭐⭐⭐⭐⭐Mise en page, animations, responsive
Bootstrap 5⭐⭐⭐⭐☆Prototypage rapide, composants prêts
JavaScript ES6+⭐⭐⭐⭐⭐Logique, DOM, APIs, async
Node.js / Express⭐⭐⭐⭐☆Backend, APIs REST, middleware
SQL / SQLite⭐⭐⭐⭐☆Persistance des données, relations
Sécurité Web⭐⭐⭐⭐☆XSS, SQL Injection, JWT, bcrypt
Git / GitHub⭐⭐⭐⭐⭐Versioning, collaboration, déploiement
Prochaines étapes : Apprenez React.js (composants UI), TypeScript (typage statique), et les concepts de CI/CD (intégration continue) pour évoluer vers un poste de développeur Full Stack senior.

Question finale : Quelle technologie côté serveur avez-vous utilisée dans ce cours pour construire des APIs REST ?

Django (Python)
Node.js / Express ✓
Laravel (PHP)
Spring Boot (Java)

Félicitations !

Vous avez terminé ce manuel de formation. Passez maintenant votre test de niveau sur NB3BN Info.

Passer le Test de Niveau