Apprends à coder,
vraiment.

Des cours structurés en développement web, Linux et DevOps — avec des exercices interactifs pour pratiquer au fur et à mesure, pas juste lire.

14cours
67chapitres
17labs interactifs
100%gratuit

Les cours disponibles

Du débutant au professionnel — chaque cours est découpé en chapitres courts, avec du code et des explications claires.

Développement
🛠️ 3 chapitres

Outils du développeur

Avant d'écrire une ligne de code, maîtrise tes outils. L'éditeur, le navigateur et le terminal : les trois piliers du quotidien d'un dev.

  • Visual Studio Code : extensions et raccourcis
  • DevTools navigateur : inspecter, débugger
  • Terminal intégré : commandes essentielles
🔀 5 chapitres

Git : Versionner son code

Ne perds plus jamais ton travail. Apprends à versionner, brancher et collaborer avec Git et GitHub, compétence incontournable sur tout projet.

  • commit, push, pull
  • Branches, merge et résolution de conflits
  • GitHub et workflow collaboratif
🌐 10 chapitres

HTML / CSS

Crée ta première page web de zéro. Structure, mise en forme, et mise en page avec Flexbox.

  • Structure d'une page HTML
  • Sélecteurs CSS et box model
  • Mise en page avec Flexbox
🟨 8 chapitres

JavaScript : les bases

Le langage incontournable du web. Apprends à manipuler les données, contrôler le flux et écrire des fonctions.

  • Variables, types et syntaxe
  • Conditions, boucles, tableaux
  • Fonctions et méthodes utiles
🧩 2 chapitres

JavaScript : les objets

Passe à la programmation orientée objet. Objets, classes, héritage : les bases de l'architecture logicielle.

  • Objets et déstructuration
  • Classes et constructeurs
  • Héritage et membres privés
🟢 3 chapitres

Node.js Backend

Fais tourner du code côté serveur, connecte une base de données et expose une API REST complète avec Express.

  • Exécution serveur vs navigateur
  • Persistance avec SQLite
  • API REST : GET, POST, PUT, DELETE
Système
🐧 8 chapitres

Linux : les bases

Maîtrise le terminal Ubuntu. Navigation, fichiers, processus, permissions, réseau, avec un terminal simulé pour pratiquer.

  • Navigation et gestion de fichiers
  • Processus, permissions, apt
  • Réseau et commandes système
🔌 5 chapitres

Linux : services réseau

Configure un vrai serveur. SSH sans mot de passe, site web avec Nginx, protection avec ufw.

  • SSH et clés de connexion
  • Nginx : serveur web et reverse proxy
  • Firewall avec ufw
DevOps
🐳 1 chapitre

Docker

Conteneurise tes applications. Images, conteneurs, volumes, réseaux et Docker Compose pour orchestrer plusieurs services.

  • Images et conteneurs
  • Dockerfile et build
  • Docker Compose multi-services
☸️ 5 chapitres

Kubernetes

Orchestre des conteneurs à grande échelle. Déploie, scale et gère tes applications dans un cluster Kubernetes.

  • Pods, Deployments, ReplicaSets
  • Services, Ingress, réseau
  • Helm : package manager K8s
🏗️ 5 chapitres

Terraform : IaC

Décris ton infrastructure en code. Crée, modifie et détruis des ressources cloud (AWS, GCP, Azure) avec un seul outil.

  • Providers, resources, variables
  • State et remote backend
  • Modules réutilisables
⚙️ 4 chapitres

Ansible

Automatise la configuration de tes serveurs. Sans agent, en YAML, Ansible gère des centaines de machines comme une seule.

  • Playbooks et tasks
  • Inventaires et variables
  • Rôles et Ansible Galaxy
📊 4 chapitres

Observabilité

Comprends ce qui se passe dans tes systèmes. Logs, métriques et traces : les trois piliers pour monitorer et débugger en production.

  • Prometheus : collecte de métriques
  • Grafana : dashboards et alertes
  • Logs avec ELK Stack et Loki
🔄 4 chapitres

CI/CD

Automatise tests et déploiements. Du commit au serveur de prod en passant par les tests, sans intervention manuelle.

  • Intégration continue vs déploiement continu
  • GitHub Actions : workflows YAML
  • Pipelines avec Docker et registres

Pourquoi DevAcademia ?

Une approche différente de l'apprentissage — active, progressive et sans blabla.

🖥️

Labs interactifs

Pour Linux, un vrai terminal simulé dans le navigateur. Tu tapes des commandes, tu obtiens de vraies réponses, tu valides des défis. On apprend en faisant.

📚

Cours structurés

Chaque chapitre suit la même logique : pourquoi ça existe, comment ça marche, puis du code commenté. Des analogies pour que tout soit clair dès la première lecture.

📈

Progression sauvegardée

Crée un compte et retrouve ton avancement. Les labs complétés sont mémorisés et visibles dans ton profil. Tu reprends là où tu t'es arrêté.

🆓

100% gratuit

Tout le contenu est accessible sans abonnement. Le compte sert uniquement à sauvegarder ta progression — rien d'autre.

Par où commencer ?

Si tu débutes, suis ce parcours dans l'ordre. Chaque étape prépare la suivante.

1
🌐
HTML / CSS
La base de tout — comprendre comment une page web est construite.
2
🟨
JavaScript : les bases
Rends tes pages dynamiques et interactives.
3
🧩
JavaScript : les objets
Passe à la POO pour structurer tes projets.
4
🐧
Linux : les bases
Maîtrise l'environnement des serveurs et du déploiement.
5
🐳
Docker
Conteneurise et déploie tes applications proprement.
🤔

Pourquoi pas juste Notepad ?

Techniquement, tu peux écrire du code dans n'importe quel éditeur de texte : Notepad, Word, même un email. Mais dès que tu ouvres un vrai éditeur de code, tu comprends pourquoi personne ne fait ça.

NotepadÉditeur de code
Tout en blanc sur noir, impossible à lireColoration syntaxique : chaque type d'élément a une couleur
Aucune aide à l'écritureAutocomplétion : l'éditeur anticipe ce que tu vas écrire
Aucune détection d'erreurErreurs soulignées en rouge avant même d'exécuter
Navigation à l'aveugle dans les fichiersExplorateur de projet, recherche globale, aller à la définition
Aucune intégration avec Git ou le terminalGit, terminal et débogueur, tout au même endroit
Un bon éditeur ne te rend pas meilleur en code, mais il te laisse te concentrer sur le code plutôt que sur l'outil. C'est le même principe qu'un bon couteau en cuisine.
🗺️

Les éditeurs qui existent

Il existe deux grandes catégories : les éditeurs légers (rapides, peu de fonctionnalités de base) et les IDE (Integrated Development Environment) qui embarquent tout : compilateur, débogueur, gestion de projet… Les IDE sont puissants mais lourds ; les éditeurs légers sont rapides mais limités. VSCode a réussi à trouver un équilibre entre les deux.

Voici les principaux outils que tu croiseras dans la vie d'un développeur :

ÉditeurTypePoints fortsLimite
Notepad++ Éditeur texte avancé Ultra-léger, rapide, Windows uniquement Pas fait pour les projets complexes
Sublime Text Éditeur léger Très rapide, multi-curseurs, interface épurée Payant pour un usage régulier
Vim / Neovim Éditeur terminal Extrêmement puissant, fonctionne en SSH, très apprécié des pro Courbe d'apprentissage raide, il faut désactiver le mode insertion pour taper "quitter"
WebStorm IDE complet Le meilleur IDE JavaScript/TypeScript, intelligence maximale Lourd, payant (~€70/an)
IntelliJ IDEA IDE complet Référence pour Java et Kotlin, support multi-langages Lourd, payant
Visual Studio Code Éditeur extensible Léger mais complet, gratuit, extensions pour tout, multi-langages Peut devenir lourd avec trop d'extensions

Pourquoi VSCode s'est imposé

VSCode a été créé par Microsoft en 2015 et est devenu en quelques années l'éditeur le plus utilisé au monde. En 2024, plus de 73% des développeurs l'utilisent (sondage Stack Overflow). Ce n'est pas un hasard.

Il fonctionne pour à peu près tout :

  • 🌐 Web : HTML, CSS, JavaScript, TypeScript, React, Vue, Angular…
  • 🐍 Python : data science, scripts, Django, FastAPI
  • Java / Kotlin : via l'extension Java Extension Pack
  • 🦀 Rust, Go, C/C++ : extensions officielles très complètes
  • 🐳 Docker / Kubernetes : édition de Dockerfile, docker-compose, gestion des containers
  • 📊 Jupyter Notebooks : notebooks Python interactifs directement dans l'éditeur
  • ☁️ Cloud : extensions AWS, Azure, GCP pour déployer sans quitter VSCode

Ce qui le distingue vraiment, c'est sa philosophie : un cœur léger et rapide, extensible à l'infini. Tu n'installes que ce dont tu as besoin. Un développeur web et un data scientist utilisent le même éditeur, avec des extensions différentes.

Téléchargement : code.visualstudio.com. Gratuit, open source, disponible sur Windows, macOS et Linux.
🎨

L'interface

Voilà à quoi ressemble VSCode quand tu l'ouvres. Ça peut paraître chargé au premier coup d'œil, mais l'interface suit une logique simple : à gauche les outils de navigation, au centre le code, en bas le terminal.

Interface Visual Studio Code
⌨️

Raccourcis indispensables

Les raccourcis clavier, c'est ce qui fait la différence entre quelqu'un qui code vite et quelqu'un qui passe son temps dans les menus. Tu n'as pas besoin de tous les retenir d'un coup — commence par les 3 premiers, le reste viendra naturellement avec la pratique.

RaccourciAction
Ctrl+POuvrir un fichier par nom (cherche dans tout le projet)
Ctrl+Shift+PPalette de commandes : accès à toutes les fonctions VSCode
Tab / Ctrl+EspaceAutocomplétion : accepter la suggestion / forcer l'affichage des suggestions
Ctrl+DSélectionner la prochaine occurrence du mot courant
Alt+↑ / ↓Déplacer une ligne vers le haut ou le bas
Shift+Alt+↓Dupliquer la ligne courante
Ctrl+/Commenter / décommenter une ligne
F2Renommer un symbole dans tout le projet
F12Aller à la définition d'une fonction
🔌

Extensions essentielles pour débuter

VSCode est léger par défaut — l'intelligence vient des extensions. Git est déjà intégré nativement, pas besoin d'extension pour ça. Pour débuter en frontend HTML/CSS et backend Node.js, voici ce qui vaut vraiment la peine :

  • Live Server : recharge la page automatiquement à chaque sauvegarde. Indispensable quand tu travailles en HTML/CSS.
  • Prettier : formate ton code proprement à chaque Ctrl+S. Fonctionne pour HTML, CSS et JavaScript.
  • ESLint : souligne les erreurs JavaScript en temps réel, avant même d'exécuter le code. Essentiel pour Node.js.
Installer une extension : Ctrl+Shift+X pour ouvrir le marketplace, tape le nom, clique sur Install. C'est actif immédiatement.
🎯

Atelier — Quiz

🎯 Quiz — Visual Studio Code

🔒

Atelier réservé aux membres

🗺️

Les navigateurs

Tous les navigateurs ne se valent pas, ni pour développer ni pour ton audience. Il y a ceux que tu utilises pour coder, et ceux qu'utilisent tes futurs utilisateurs.

Pour développer, Chrome et Firefox sont les meilleurs choix. Leurs DevTools sont les plus complètes et les mieux documentées. Chrome a tendance à être le choix par défaut de la majorité des développeurs. Firefox se distingue par son inspecteur CSS particulièrement puissant.

Pour ton public, les parts de marché mondiales donnent une image claire de ce qu'il faut tester :

NavigateurPart de marchéPoints à surveiller
Chrome~65%Référence — si ça marche ici, ça marche souvent partout
Safari~18%Dominant sur iPhone/iPad — CSS parfois en avance ou en retard
Edge~5%Basé sur Chrome, quasiment identique
Firefox~3%Petit mais fidèle — profil technique
Samsung Internet~2%Présent sur Android bas de gamme
Safari est basé sur WebKit, un moteur différent de celui de Chrome (Blink). C'est pour ça que certaines propriétés CSS ou fonctions JavaScript se comportent différemment sur iPhone. Tester sur Safari est obligatoire si ton site a des utilisateurs mobile.
🔍

L'onglet Elements / Inspector

Inspecte et modifie le HTML et le CSS en temps réel. Les modifications sont temporaires (rechargement = retour à l'original) — parfait pour tester des idées avant de les coder.

  • Clic droit → Inspecter sur n'importe quel élément pour l'ouvrir directement
  • Double-clique sur une valeur CSS pour la modifier en live
  • La boîte du bas montre le Box Model visuel (margin, padding, border)
💬

L'onglet Console

La console affiche les erreurs JavaScript et les console.log(). Elle est aussi un interpréteur JavaScript interactif : tu peux y taper du code directement pour tester des expressions ou manipuler la page.

// Dans la console du navigateur
document.title = "Mon titre modifié"
document.body.style.background = "purple"
document.querySelectorAll("p").length   // compte les paragraphes
Les erreurs JS apparaissent en rouge avec le fichier et le numéro de ligne. Clique dessus pour aller directement au code concerné.
🌐

L'onglet Network

L'onglet Network enregistre toutes les requêtes HTTP faites par la page : les fichiers chargés (HTML, CSS, images, scripts), mais aussi tous les appels API déclenchés par ton JavaScript. Pour chaque requête tu vois le code de statut (200, 404, 500…), le temps de chargement, la taille de la réponse, et le contenu exact échangé. C'est l'endroit où tu vas quand ton appel API ne renvoie pas ce que tu attends.

Essaie dès maintenant : ouvre n'importe quel site, lance l'onglet Network et recharge la page. Tu verras défiler toutes les ressources chargées une par une. Sur un site qui fait des appels API, tu pourras lire exactement ce que le serveur renvoie, sans avoir à toucher au code.

Filtre par Fetch/XHR pour n'afficher que les appels API et masquer le reste du bruit. Clique ensuite sur une requête pour inspecter son body, ses headers et sa réponse complète.
📱

Vue responsive (Device Toolbar)

La Device Toolbar te permet de tester ton site sur différents appareils sans quitter le navigateur. Active-la avec Ctrl+Shift+M ou l'icône mobile en haut des DevTools. Tu peux sélectionner un appareil précis dans la liste (iPhone 14, Galaxy S20, iPad…) ou définir manuellement une largeur d'écran. Le navigateur simule alors exactement ce que verrait un utilisateur sur cet appareil — dimensions, ratio de pixels et media queries CSS inclus. Très utile pour vérifier qu'un menu ne déborde pas sur mobile ou qu'un layout Flexbox se comporte bien sur petits écrans, sans avoir à sortir son téléphone à chaque modification.

🎯

Atelier — Quiz

🎯 Quiz — Les outils du navigateur

🔒

Atelier réservé aux membres

🚀

Ouvrir et utiliser le terminal

Pour ouvrir le terminal, va dans le menu Terminal en haut de VSCode puis clique sur New Terminal. C'est tout.

Il s'ouvre directement dans le dossier de ton projet — le dossier que tu as ouvert dans VSCode. Pas besoin de naviguer jusqu'à ton projet, tu es déjà au bon endroit. Si tu as plusieurs dossiers dans ton workspace ou que tu veux aller dans un sous-dossier, tu utilises cd nom-du-dossier pour descendre et cd .. pour remonter d'un niveau.

Tu peux ouvrir plusieurs terminaux en même temps — un pour lancer ton serveur Node, un autre pour tes commandes Git. Utilise le + en haut à droite du terminal pour en créer un nouveau.
📋

Commandes essentielles

Ces commandes fonctionnent dans tous les terminaux (bash, zsh, PowerShell). Ce sont les fondamentaux que tu utiliseras quotidiennement.

CommandeEffet
pwdAffiche le répertoire courant
ls / dirListe les fichiers du répertoire courant
cd dossierChange de répertoire
cd ..Remonte au dossier parent
mkdir nomCrée un nouveau dossier
touch fichierCrée un fichier vide (Linux/Mac)
node fichier.jsExécute un script JavaScript avec Node.js
npm installInstalle les dépendances d'un projet Node.js
npm run devDémarre le serveur de développement
Ctrl+CInterrompt le processus en cours (arrêter un serveur…)
💡

Astuces de productivité

  • Flèche ↑ — rappelle la commande précédente (historique)
  • Tab — autocomplétion des noms de fichiers et dossiers
  • Ctrl+L — efface le terminal (comme clear)
  • Clic sur un chemin de fichier dans la sortie — ouvre le fichier directement dans l'éditeur
  • Plusieurs terminaux — un pour le serveur Node, un pour Git, un libre pour les tests
Git dans le terminal VSCode : tu peux taper toutes tes commandes git (git add, git commit, git push) directement ici, sans outil graphique.
🎮

Atelier — Prends en main le terminal

Le terminal s'est ouvert dans ton projet. Essaie les 3 commandes de base : savoir où tu es, voir ce qu'il y a, et naviguer dans un sous-dossier. Tape hint si tu es bloqué.

🔒

Atelier réservé aux membres

Connectez-vous pour accéder aux ateliers interactifs.

🎮 Lab — Terminal VSCode (3 défis)
Terminal — mon-projet
🤔

Pourquoi versionner ?

Sans Git, tu aurais probablement des dossiers nommés projet_final, projet_final_v2, projet_final_VRAIMENT_FINAL… Git remplace ça par un historique propre, navigable, et sans risque de perdre du travail.

Sans GitAvec Git
Copier le dossier avant chaque modifUn git commit sauvegarde l'état
Envoyer des .zip par email pour collaborerUn git push partage le code
Impossible de revenir en arrièregit revert annule n'importe quel commit
Conflits silencieux si deux personnes éditentGit détecte et signale les conflits explicitement
📦

Les 3 zones de Git

Git fonctionne avec 3 zones distinctes. Les deux premières sont locales, sur ton ordinateur : le Working Directory où tu édites tes fichiers, et la Staging Area (zone de transit) où tu prépares exactement ce que tu veux enregistrer. La troisième, le dépôt local, est aussi sur ta machine et conserve tout l'historique de tes commits. Enfin, il y a le dépôt distant — lui est hébergé sur un serveur externe comme GitHub ou GitLab. Ce sont des plateformes web qui hébergent ton code Git en ligne, te permettent de collaborer avec d'autres développeurs, et servent de sauvegarde distante de ton projet.

flowchart LR
    WD["🗂️ Working Directory
Tes fichiers"] SA["📋 Staging Area
Zone de transit"] RL["💾 Dépôt local
Repository"] GH["☁️ GitHub / GitLab
Dépôt distant"] WD -->|git add| SA SA -->|git commit| RL RL -->|git push| GH
La zone de transit (staging area) est ce qui rend Git puissant : tu choisis précisément quelles modifications inclure dans un commit, même si tu as modifié 10 fichiers.
⚙️

Installation et configuration

Commence par vérifier si Git est déjà installé sur ton machine. Ouvre un terminal et tape :

git --version
# git version 2.47.0  ← si Git est installé, tu vois ça
# 'git' n'est pas reconnu...  ← sinon, il faut l'installer

Si Git n'est pas encore installé :

SystèmeComment installer
WindowsTélécharge l'installeur sur git-scm.com et lance-le. Garde toutes les options par défaut. Redémarre VSCode après.
macOSGit est souvent déjà présent. Sinon, installe Homebrew puis tape brew install git dans le terminal.
Linux (Ubuntu/Debian)sudo apt update && sudo apt install git

Une fois Git installé, configure ton identité. C'est obligatoire car chaque commit enregistre ton nom et email :

# Configuration initiale (à faire une seule fois sur chaque machine)
git config --global user.name "Ton Prénom Nom"
git config --global user.email "ton@email.com"
git config --global core.editor "code --wait"   # VSCode comme éditeur Git
git config --global init.defaultBranch main         # branche principale = main
Ces 4 lignes sont à faire une seule fois. Git les réutilise ensuite pour tous tes projets. Ton nom et email s'afficheront dans l'historique de chaque commit, visibles par toute l'équipe sur GitHub.
💜

Git dans VSCode

VSCode intègre Git nativement, sans aucune extension. L'icône Source Control dans la barre latérale gauche (ou Ctrl+Shift+G) donne accès à une interface graphique complète pour tout ce qu'on peut faire avec Git.

Dans cet onglet tu peux :

  • Voir les fichiers modifiés — ceux en attente de commit apparaissent avec un indicateur coloré
  • Stager des fichiers — clique sur le + à côté d'un fichier pour l'ajouter à la zone de transit (équivalent de git add)
  • Écrire et valider un commit — tape le message en haut, clique sur ✓ Commit
  • Push et pull — via le menu ou le bouton de synchronisation en bas de VSCode
  • Voir les branches — le nom de la branche courante apparaît en bas à gauche, clique dessus pour en changer
L'onglet Source Control et le terminal font exactement la même chose, ils parlent au même Git. Tu peux passer de l'un à l'autre selon ta préférence. Beaucoup de développeurs utilisent le terminal pour les commandes courantes (status, add, commit) et l'interface VSCode pour visualiser les diff ou gérer des conflits.
🚀

Le flux de base

Travailler avec Git suit toujours le même cycle. Tu modifies des fichiers dans ton projet, tu choisis lesquels inclure dans la prochaine sauvegarde, puis tu valides. C'est tout.

# 1. Initialiser un dépôt Git dans ton projet
git init

# 2. Voir l'état du dépôt (fichiers modifiés, non suivis...)
git status

# 3. Ajouter des fichiers à la zone de transit
git add index.html      # un fichier précis
git add .              # tout le dossier courant

# 4. Valider avec un message
git commit -m "Premier commit : structure de base"

# 5. Voir l'historique des commits
git log
Un bon message de commit explique ce qui a changé et pourquoi, pas comment. "Ajoute la page de contact" est un bon message. "Modifications" ou "fix" sont mauvais — dans 6 mois tu ne sauras plus ce que tu as fait.
🎮

Atelier — Tes premiers commits

Tu es dans un projet web déjà initialisé avec Git. Il y a des fichiers non suivis. Fais ton premier commit en 3 étapes. Tape hint si tu es bloqué.

🔒

Atelier réservé aux membres

Connectez-vous pour accéder aux ateliers interactifs.

🎮 Lab — Premiers pas avec Git (3 défis)
bash — projets/web
▶️

Initialiser et sauvegarder

git init crée un dossier caché .git/ à la racine de ton projet — c'est là que Git stocke tout l'historique. Tu ne touches jamais à ce dossier directement. Ensuite, le cycle se répète à chaque session de travail : tu vérifies l'état, tu choisis ce que tu valides, et tu crées un commit.

# Initialiser Git dans un dossier existant
git init

# Voir l'état actuel (fichiers modifiés, en attente, etc.)
git status

# Ajouter un fichier spécifique à la zone de transit
git add index.html

# Ajouter TOUS les fichiers modifiés du dossier courant
git add .

# Créer un commit — la sauvegarde dans l'historique
git commit -m "feat: ajout de la page d'accueil"
Convention de messages de commit : commence par un préfixe — feat: (nouvelle fonctionnalité), fix: (correction de bug), docs: (documentation), style: (mise en forme sans changement de logique). Ça rend l'historique lisible d'un coup d'œil, surtout en équipe.
🔍

Consulter l'historique

Chaque commit reçoit un identifiant unique (un hash comme a3f8c12) généré automatiquement. git log te permet de parcourir l'historique entier du projet, de voir qui a fait quoi et quand. git diff te montre exactement ce qui a changé ligne par ligne — en vert ce qui est ajouté, en rouge ce qui est supprimé.

# Historique complet des commits
git log

# Version condensée : une ligne par commit (plus lisible)
git log --oneline

# Voir les modifications non encore ajoutées (avant git add)
git diff index.html

# Voir ce qui est en zone de transit (après git add, avant commit)
git diff --staged

# Annuler les modifications d'un fichier (revenir au dernier commit)
git restore index.html
git restore est irréversible si le fichier n'a jamais été commité — les modifications disparaissent définitivement. Utilise-le uniquement quand tu es sûr de vouloir abandonner tes changements.
🏷️

Le fichier .gitignore

Certains fichiers ne doivent jamais être versionnés : mots de passe dans un .env, dépendances dans node_modules/ (qui peut peser des centaines de Mo), fichiers de build générés automatiquement… Le fichier .gitignore à la racine du projet indique à Git quels fichiers ou dossiers ignorer complètement.

# .gitignore — exemple pour un projet Node.js
node_modules/
.env
.env.local
dist/
*.log
.DS_Store        # macOS uniquement
Si tu as déjà commité un fichier par erreur et que tu l'ajoutes ensuite au .gitignore, Git continuera quand même à le suivre. Il faut d'abord le retirer du suivi avec git rm --cached nomfichier, puis commiter ce changement.
Le site gitignore.io génère automatiquement un .gitignore complet adapté à ton stack — Node, Python, Java, etc. Tape simplement les technologies que tu utilises.
🎮

Atelier — add, commit, log

Un commit existe déjà dans ce projet. De nouveaux fichiers ont été ajoutés depuis. À toi de faire le prochain commit et de vérifier l'historique. Tape hint si tu es bloqué.

🔒

Atelier réservé aux membres

Connectez-vous pour accéder aux ateliers interactifs.

🎮 Lab — Git : add, commit, log (3 défis)
bash — projets/web
💡

Pourquoi utiliser des branches ?

Sans branche, tu travailles directement sur main — la branche principale. Ça fonctionne quand tu es seul, mais dès que tu collabores ou que tu veux tester quelque chose sans risquer de casser ce qui marche, les branches deviennent indispensables.

Le principe : tu pars de main, tu crées une branche dédiée à ta fonctionnalité, tu travailles dessus librement, et tu ramènes le tout dans main une fois terminé. Si ça tourne mal, tu supprimes simplement la branche.

gitGraph
   commit id: "init"
   commit id: "feat: accueil"
   branch feature/login
   checkout feature/login
   commit id: "feat: formulaire"
   commit id: "fix: validation"
   checkout main
   merge feature/login id: "merge login"
   commit id: "fix: typo nav"
      
Le nom main est la convention moderne pour la branche principale (anciennement master). GitHub utilise main par défaut depuis 2020. Sur les vieux projets tu trouveras encore master — les deux se comportent exactement pareil.
🌿

Créer et naviguer entre branches

Chaque branche a un nom. La convention la plus répandue est feature/nom pour les nouvelles fonctionnalités et fix/nom pour les corrections. L'étoile dans git branch indique la branche sur laquelle tu te trouves.

# Lister toutes les branches (l'étoile = branche courante)
git branch

# Créer une nouvelle branche (sans y basculer)
git branch feature/login

# Basculer sur une branche existante
git switch feature/login

# Créer ET basculer en une seule commande (le plus utilisé)
git switch -c feature/login

# Supprimer une branche après fusion
git branch -d feature/login
Utilise toujours git switch -c feature/ma-feature — créer et basculer en une commande. C'est rare de créer une branche sans vouloir y travailler immédiatement.
🔀

Fusionner (merge)

Quand ta fonctionnalité est prête, tu ramènes les commits de ta branche dans main. La règle : on se positionne toujours sur la branche qui reçoit les changements, puis on précise celle qu'on fusionne.

# 1. Revenir sur la branche de destination
git switch main

# 2. Fusionner la branche de travail dans main
git merge feature/login

Si deux personnes ont modifié la même zone du même fichier sur des branches différentes, Git ne sait pas quelle version garder — c'est un conflit. Il marque les zones problématiques directement dans le fichier :

<<<<<<< HEAD
  const titre = "Accueil";
=======
  const titre = "Home";
>>>>>>> feature/login

Tu ouvres le fichier dans VSCode (qui colore les conflits), tu choisis quelle version garder, tu supprimes les marqueurs <<<, === et >>>, puis tu commites la résolution :

git add fichier-en-conflit.js
git commit -m "merge: résolution conflit login"
Ne jamais commiter avec les marqueurs de conflit encore dans le fichier. Les tests vont planter et tes collègues vont trouver du code illisible en prod.
🎮

Atelier — Créer et naviguer entre branches

Ce projet a deux branches. Explore-les et crée-en une nouvelle. Tape hint si tu es bloqué.

🔒

Atelier réservé aux membres

Connectez-vous pour accéder aux ateliers interactifs.

🎮 Lab — Git : Branches (3 défis)
bash — projets/web (main)
🌍

Local et distant — comment ça s'articule

Ton ordinateur et GitHub ne se synchronisent pas automatiquement. C'est toi qui décides quand envoyer tes commits (push) et quand récupérer ceux des autres (pull). Les deux dépôts peuvent donc être temporairement décalés — c'est normal et voulu.

flowchart LR
    L["💻 Dépôt local
tes commits"] R["☁️ GitHub / GitLab
dépôt distant"] A["💻 Autre développeur"] L -->|"git push"| R R -->|"git pull"| L R -->|"git clone"| A A -->|"git push"| R
GitHub et Git sont deux choses différentes. Git est l'outil de versioning qui tourne sur ta machine — il a été créé par Linus Torvalds en 2005. GitHub est une plateforme web qui héberge des dépôts Git — rachetée par Microsoft en 2018. On pourrait utiliser Git sans GitHub (avec GitLab, Bitbucket, ou un serveur personnel).
🔗

Connecter un dépôt distant

Il y a deux situations : soit tu pars d'un dépôt existant sur GitHub (tu le clones — tout est configuré automatiquement), soit tu as déjà un dépôt local et tu veux le connecter à un dépôt GitHub vide que tu viens de créer. origin est juste le nom conventionnel donné au remote principal — tu pourrais l'appeler autrement, mais personne ne le fait.

# Scénario A — partir de GitHub : clone le projet complet
git clone https://github.com/user/projet.git
# → crée un dossier "projet/", télécharge tout l'historique,
#   et configure "origin" automatiquement

# Scénario B — relier un projet local à GitHub
git remote add origin https://github.com/user/projet.git

# Vérifier les remotes configurés
git remote -v
🔄

Push, pull, fetch

git push envoie tes commits locaux vers GitHub. git pull récupère les commits du remote et les applique immédiatement à ta branche locale — c'est en réalité un git fetch suivi d'un git merge. Si tu veux juste voir ce qui a changé sur le remote sans modifier tes fichiers, utilise git fetch seul.

# Envoyer ses commits vers GitHub
git push origin main

# Premier push d'une nouvelle branche (définit le tracking)
git push -u origin feature/login
# → après ça, un simple "git push" suffit pour cette branche

# Récupérer ET appliquer les commits du remote
git pull origin main

# Récupérer sans appliquer (juste voir ce qui a changé)
git fetch origin
Toujours git pull avant de git push quand tu collabores. Si quelqu'un a pushé pendant que tu travaillais, Git refusera ton push tant que tu n'as pas intégré ses changements. Pull d'abord, résous les conflits si nécessaire, puis push.
🎯

Atelier — Quiz

🎯 Quiz — GitHub & dépôt distant

🔒

Atelier réservé aux membres

💡

C'est quoi GitHub Flow ?

GitHub Flow est une convention de travail en équipe bâtie sur Git et GitHub. Ce n'est pas un outil, c'est un accord : tout le monde sur l'équipe suit les mêmes règles pour créer des branches, soumettre des modifications et les intégrer. GitHub l'a formalisé et documenté comme standard pour ses propres équipes.

Il repose sur une règle centrale : main est toujours déployable. On ne touche jamais directement à main — tout passe par une branche et une Pull Request revue par un collègue avant le merge.

Il existe un autre workflow appelé Git Flow (créé par Vincent Driessen en 2010) qui utilise plusieurs branches de long terme : main, develop, release, hotfix… Il est plus structuré mais aussi plus complexe. GitHub Flow est plus léger et convient à la majorité des équipes qui font du déploiement continu.
🔄

Le cycle en 6 étapes

Chaque fonctionnalité ou correction suit toujours le même chemin, de la branche au merge en passant par la Pull Request.

gitGraph
   commit id: "main stable"
   branch feature/recherche
   checkout feature/recherche
   commit id: "feat: barre de recherche"
   commit id: "feat: résultats"
   checkout main
   merge feature/recherche id: "PR #42 mergée"
   branch fix/typo-nav
   checkout fix/typo-nav
   commit id: "fix: correction menu"
   checkout main
   merge fix/typo-nav id: "PR #43 mergée"
      
  1. Créer une branche depuis main à jour, avec un nom descriptif (feature/… ou fix/…)
  2. Committer régulièrement sur cette branche — chaque commit doit compiler et rester cohérent
  3. Pousser la branche sur GitHub pour la partager et la sauvegarder
  4. Ouvrir une Pull Request — tu demandes à l'équipe de relire ton code avant qu'il rejoigne main
  5. Revue de code — les collègues commentent, suggèrent, approuvent
  6. Merge dans main une fois approuvée — puis supprimer la branche
💬

La Pull Request

La Pull Request (PR) est le mécanisme central de GitHub Flow. Ce n'est pas une commande Git — c'est une fonctionnalité de GitHub (et GitLab l'appelle Merge Request). Elle permet de demander formellement l'intégration de ta branche dans main.

Une PR, c'est à la fois une revue de code et une conversation : on voit exactement quelles lignes ont changé, on peut commenter ligne par ligne, demander des modifications, approuver, et déclencher des tests automatiques avant le merge. Rien ne rentre dans main sans être passé par là.

Une PR courte (moins de 400 lignes modifiées) est beaucoup mieux revue qu'une PR de 2000 lignes. Découpe tes fonctionnalités en petits incréments — chacun mérite sa propre PR.
⌨️

Les commandes du cycle complet

# 1. Partir d'un main à jour
git switch main
git pull

# 2. Créer une branche pour la fonctionnalité
git switch -c feature/ajout-recherche

# 3. Travailler et committer régulièrement
git add .
git commit -m "feat: barre de recherche avec résultats live"

# 4. Pousser la branche sur GitHub
git push -u origin feature/ajout-recherche

# 5. Sur GitHub : ouvrir une Pull Request, attendre la revue, merger

# 6. Après merge : revenir sur main, récupérer les changements, nettoyer
git switch main
git pull
git branch -d feature/ajout-recherche
🛟

Commandes de secours

Même les développeurs expérimentés font des erreurs. Ces commandes sont ton filet de sécurité — mieux vaut les connaître avant d'en avoir besoin.

SituationCommande
Mettre de côté des modifs sans committergit stash / git stash pop
Annuler le dernier commit (garde les fichiers)git reset HEAD~1
Créer un commit qui annule un commit passégit revert <hash>
Voir qui a écrit chaque ligne d'un fichiergit blame fichier.js
Commits fréquents = filet de sécurité. Committe chaque fois que tu atteins un état qui compile et fonctionne, même si c'est toutes les 20 minutes. C'est toujours récupérable tant que c'est commité.
🎯

Atelier : Quiz

🎯 Quiz : GitHub Flow

🔒

Atelier réservé aux membres

💡

Pourquoi JavaScript ?

HTML structure le contenu, CSS le met en forme. Mais tout ça reste statique. JavaScript ajoute le comportement : réagir à un clic, valider un formulaire, afficher ou cacher un menu, charger des données depuis un serveur, animer des éléments…

TechnologieRôle
HTMLStructure et contenu
CSSApparence et mise en page
JavaScriptComportement et interactivité

JavaScript est aussi le langage le plus utilisé au monde. Il fonctionne dans le navigateur (frontend) mais aussi sur un serveur avec Node.js (voir le cours Node.js Backend), dans des applications mobiles, des extensions navigateur, des outils CLI et même des applications desktop.

📄

Créer et lier un fichier JavaScript

Un fichier JavaScript porte l'extension .js. Par convention, on l'appelle app.js, main.js ou selon son rôle. Pour le lier à une page HTML, on utilise la balise <script> dans le <head> avec l'attribut defer :

<!DOCTYPE html>
<html lang="fr">
<head>
  <meta charset="UTF-8">
  <title>Mon site</title>
  <link rel="stylesheet" href="style.css">
  <script src="app.js" defer></script>  <!-- defer = attend que la page soit chargée -->
</head>
<body>
  <h1>Bonjour !</h1>
</body>
</html>
defer est important : sans lui, le script se chargerait avant que la page soit construite, et ton code ne trouverait pas les éléments HTML qu'il cherche. Avec defer, le fichier est téléchargé en parallèle et s'exécute une fois la page complète.

Ton dossier de projet ressemble alors à ça :

mon-site/
  index.html
  style.css
  app.js        <-- ton code JavaScript
🌐

Frontend ou Backend ?

JavaScript peut vivre à deux endroits très différents :

Frontend (navigateur)Backend (Node.js)
Où tourne le codeDans le navigateur de l'utilisateurSur un serveur
Ce qu'il peut faireModifier la page, réagir aux clics, appeler des APIsLire des fichiers, accéder à une base de données, exposer une API
Comment le lancerOuvre le fichier HTML dans un navigateurnode app.js dans le terminal

Dans ce cours on travaille exclusivement en frontend. Pour le backend, on a un cours dédié — Node.js Backend — qui reprend exactement les mêmes bases JS et les applique côté serveur avec Express et une base de données.

🔍

Voir son code s'exécuter

Le réflexe de base pour tester du JavaScript est console.log(). Il affiche ce que tu veux dans la console, sans rien changer à la page.

console.log("Bonjour !");          // affiche une chaîne
console.log(42);                   // affiche un nombre
console.log("Valeur :", maVariable); // affiche du texte + une variable

Dans le navigateur : ouvre les DevTools avec F12 (ou clic droit → Inspecter) puis clique sur l'onglet Console. Toutes les sorties console.log et les erreurs y apparaissent en temps réel. Tu peux aussi y taper du JavaScript directement pour le tester à la volée.

Dans VSCode : le débogueur JavaScript est intégré nativement. Pour l'utiliser :

  1. Clique dans la marge gauche d'une ligne pour poser un point d'arrêt (breakpoint) — un cercle rouge apparaît
  2. Lance le débogueur avec F5 (ou menu Run → Start Debugging)
  3. Le code s'arrête à ce point et tu peux inspecter la valeur de chaque variable, avancer ligne par ligne, et comprendre exactement ce qui se passe
Erreur dans la console ? C'est normal au début. Lis le message d'erreur jusqu'au bout — il indique toujours le fichier et la ligne concernés. Une erreur is not defined signifie que tu appelles quelque chose qui n'existe pas encore. Une TypeError signifie que tu utilises une valeur d'un mauvais type.
🎯

Atelier — Quiz

🎯 Quiz : Introduction au JavaScript

🔒

Atelier réservé aux membres

💡

Qu'est-ce qu'une variable ?

En programmation, une variable est un conteneur qui stocke une donnée dans la mémoire de l'ordinateur. Elle possède un nom (pour la retrouver), une valeur (la donnée stockée) et un type (la nature de la donnée).

Analogie : Imaginez la mémoire de l'ordinateur comme une grande armoire avec des milliers de tiroirs. Chaque tiroir a une étiquette (le nom de la variable) et contient une donnée (la valeur). Quand vous voulez retrouver le nom d'un utilisateur, vous ouvrez directement le tiroir nomUtilisateur.

Il existe plusieurs types de données fondamentaux :

TypeDescriptionExemple
stringTexte, entre guillemets"Bonjour"
numberNombre entier ou décimal42, 3.14
booleanVrai ou faux uniquementtrue, false
nullAbsence de valeur (volontaire)null
undefinedVariable déclarée sans valeurundefined
📝

Déclarer une variable : let et const

En JavaScript moderne, on déclare une variable avec let ou const. La différence est simple : let peut être réassigné, const est figé une fois déclaré.

Concrètement, une variable est un nom qui fait référence à une case mémoire contenant la valeur. Le programme utilise le nom pour retrouver et lire (ou modifier) ce qui est stocké dedans :

Nom de la variable
Case mémoire
prenom
──→
"Alice"
age
──→
28
estConnecte
──→
true
// let — la valeur peut changer
let monAge = 42;
monAge = 43;            // OK, on réassigne

// const — la valeur est fixe
const monPrenom = "Alice";
// monPrenom = "Bob";    // ERREUR : on ne peut pas changer une const

// Vérifier le type d'une variable
console.log(typeof monAge);    // "number"
console.log(typeof monPrenom); // "string"
Bonne pratique : Utilisez toujours const par défaut. Passez à let seulement si vous savez que la valeur va changer. Cela rend le code plus prévisible et évite les bugs.
Et var ? L'instruction var est l'ancienne façon de déclarer des variables. Elle fonctionne encore, mais elle a des comportements surprenants (portée fonction, hoisting). Évitez-la dans du code moderne — vous la verrez parfois dans de vieux projets.
🧵

Insérer des variables dans du texte

Quand tu veux afficher un message qui contient des variables, la première idée est de coller les morceaux avec +. Ça fonctionne, mais c'est vite illisible :

const prenom = "Alice";
const age = 28;

// Avec + : difficile à lire, facile d'oublier un espace
console.log("Bonjour " + prenom + ", tu as " + age + " ans.");

La solution s'appelle les template literals. L'idée est simple : au lieu de guillemets, tu ouvres et fermes ton texte avec des backticks (`), et tu places tes variables directement dans le texte entre ${ }. JavaScript remplace automatiquement ${prenom} par la valeur de la variable prenom.

Où est le backtick ? Sur clavier AZERTY : AltGr + 7. Sur clavier QWERTY anglais : touche à gauche du 1. Ce n'est pas un guillemet ordinaire — c'est un accent grave seul.
// Avec template literal : le texte se lit d'un seul coup
console.log(`Bonjour ${prenom}, tu as ${age} ans.`);

Ce qu'on écrit entre ${ } n'est pas du texte : c'est du JavaScript pur. Tu peux y mettre une variable, mais aussi un calcul ou n'importe quelle expression :

console.log(`Dans 10 ans, tu auras ${age + 10} ans.`);
console.log(`Prénom en majuscules : ${prenom.toUpperCase()}`);
✏️

Bien nommer ses variables

Un code se lit comme un texte. Si vous nommez vos variables x, a ou truc, vous serez incapable de relire votre code dans six mois — et vos collègues non plus. Un bon nom de variable décrit précisément ce qu'elle contient.

En JavaScript, la convention est le camelCase : le premier mot en minuscule, les suivants avec une majuscule.

// Mauvais nommage
let x = "Alice";
let n = 42;
let truc = true;

// Bon nommage (camelCase)
const nomUtilisateur = "Alice";
let nombreDeProduits = 42;
const estConnecte = true;
🎯

Atelier — Éditeur de code

✏️ Fiddle — Variables

🔒

Atelier réservé aux membres

📋 Déclare 3 variables : prenom (string), age (number), estEtudiant (boolean). Affiche-les dans une phrase avec un template literal. Puis affiche le typeof de chaque variable.

JavaScript
Résultat
🔗

Intégrer JavaScript dans une page web

JavaScript s'intègre dans une page HTML de deux façons. La première : directement dans la page avec une balise <script>. La seconde, et la plus recommandée : dans un fichier .js séparé, lié à la page avec un attribut src.

<!-- Méthode 1 : code inline (déconseillé pour les gros projets) -->
<script>
  console.log("Bonjour !");
</script>

<!-- Méthode 2 : fichier externe (recommandé) -->
<script src="app.js"></script>
Où placer la balise <script> ? Toujours juste avant </body>, à la fin du HTML. Ainsi, le navigateur charge d'abord le contenu de la page avant d'exécuter le JavaScript — la page s'affiche plus vite et votre script accède au DOM sans problème.
💬

Les commentaires

Un commentaire est du texte ignoré par le navigateur. Il sert à expliquer le code pour vous-même et vos collègues. Prenez l'habitude d'en écrire : votre futur vous vous remerciera.

// Commentaire sur une seule ligne

/* Commentaire
   sur plusieurs
   lignes */

const tva = 0.20; // Taux de TVA français (20%)
🔢

Les points-virgules

En JavaScript, le point-virgule en fin d'instruction est optionnel : le langage l'insère automatiquement dans la plupart des cas. Les deux écritures sont valides :

let nom = "Alice"    // sans point-virgule
let age = 25;        // avec point-virgule
L'essentiel est d'être cohérent dans un projet. Choisissez un style au départ et gardez-le. La plupart des équipes utilisent des outils (ESLint, Prettier) qui formatent automatiquement le code.
🧩

Les accolades : délimiter des blocs

Les accolades { } délimitent un bloc d'instructions — un groupe de lignes qui forment une unité logique. Vous les verrez partout : dans les conditions, les boucles, les fonctions et les classes.

L'indentation (le décalage vers la droite avec des espaces) n'est pas obligatoire pour JavaScript, mais elle est indispensable pour que votre code soit lisible. La convention est de 2 espaces par niveau.

if (age >= 18) {         // ouverture du bloc
  console.log("Majeur"); // indenté de 2 espaces
}                          // fermeture du bloc

function saluer() {
  const msg = "Bonjour"; // à l'intérieur de la fonction
  console.log(msg);
}
🔍

console.log() — votre meilleur outil

console.log() affiche des valeurs dans la console du navigateur (accessible avec F12 → onglet Console). C'est l'outil de débogage le plus utilisé : à chaque fois que vous vous demandez "quelle est la valeur de cette variable ?", utilisez-le.

const prix = 49.90;
console.log(prix);                      // 49.9
console.log("Le prix est :", prix);    // Le prix est : 49.9
console.log(typeof prix);              // "number"
🔡

La casse : JavaScript est sensible aux majuscules

JavaScript distingue les majuscules des minuscules. maVariable, MaVariable et mavariable sont trois variables différentes. Une faute de casse est l'une des erreurs les plus fréquentes chez les débutants.

const prenom = "Alice";
console.log(prenom);   // "Alice" ✓
console.log(Prenom);   // ReferenceError: Prenom is not defined ✗
🎯

Atelier — Éditeur de code

✏️ Fiddle — Syntaxe de base

🔒

Atelier réservé aux membres

📋 Écris un commentaire en haut expliquant ce que fait le script. Déclare une const pour ton prénom et un let pour ton âge. Modifie l'âge, puis affiche les deux variables et leur typeof.

JavaScript
Résultat
💡

Pourquoi des conditions ?

Un programme doit souvent prendre des décisions : afficher un message de bienvenue différent selon l'heure, bloquer l'accès à une page si l'utilisateur n'est pas connecté, appliquer une réduction si le panier dépasse un certain montant… Tout cela se fait avec des conditions.

Analogie : C'est exactement comme dans la vie réelle. Vous vous dites "s'il pleut, je prends un parapluie ; sinon, je mets des lunettes de soleil". En JavaScript, c'est pareil — si une condition est vraie, on exécute un bloc de code ; sinon, on en exécute un autre.
🔍

if / else if / else

La structure de base : if teste une condition. Si elle est vraie, son bloc s'exécute. else if permet de tester d'autres cas. else est le cas par défaut si aucune condition n'est vraie.

const score = 75;

if (score >= 90) {
  console.log("Excellent !");
} else if (score >= 70) {
  console.log("Bien !");       // ← ce bloc s'exécute
} else if (score >= 50) {
  console.log("Passable");
} else {
  console.log("Insuffisant");
}
⚖️

Les opérateurs de comparaison

Une condition retourne toujours true ou false. Pour comparer des valeurs, on utilise des opérateurs de comparaison :

OpérateurSignificationExempleRésultat
===Égal (valeur ET type)5 === 5true
!==Différent5 !== 3true
>Supérieur10 > 5true
<Inférieur3 < 5true
>=Supérieur ou égal5 >= 5true
<=Inférieur ou égal3 <= 5true
=== et non == ! L'opérateur == compare uniquement les valeurs en convertissant les types, ce qui provoque des résultats surprenants : "5" == 5 retourne true. Utilisez toujours === qui vérifie aussi le type.
🔗

Combiner des conditions : && et ||

On peut combiner plusieurs conditions avec && (ET logique) et || (OU logique). Le symbole ! inverse une condition.

const age = 25;
const aUnPermis = true;

// && : les DEUX conditions doivent être vraies
if (age >= 18 && aUnPermis) {
  console.log("Peut conduire");
}

// || : AU MOINS UNE condition doit être vraie
if (age < 12 || age > 65) {
  console.log("Tarif réduit");
}

// ! : inverse la condition
if (!aUnPermis) {
  console.log("Ne peut pas conduire");
}
🎛️

switch — tester une valeur parmi plusieurs cas

Quand vous avez beaucoup de cas à tester sur une même variable, switch est plus lisible qu'une longue suite de else if. N'oubliez pas le break à la fin de chaque cas, sinon l'exécution continue dans le cas suivant.

const jour = "lundi";

switch (jour) {
  case "lundi":
    console.log("Début de semaine !");
    break;
  case "vendredi":
    console.log("Bientôt le week-end !");
    break;
  case "samedi":
  case "dimanche":             // les deux cas partagent le même code
    console.log("Week-end !");
    break;
  default:
    console.log("Milieu de semaine");
}

L'opérateur ternaire — la condition en une ligne

Pour un simple if/else qui retourne une valeur, l'opérateur ternaire permet d'écrire tout ça en une seule ligne : condition ? valeurSiVrai : valeurSiFaux.

const age = 20;

// Version longue
let statut;
if (age >= 18) { statut = "majeur"; }
else { statut = "mineur"; }

// Version ternaire (équivalent)
const statut2 = age >= 18 ? "majeur" : "mineur";
console.log(statut2); // "majeur"
🎯

Atelier — Éditeur de code

✏️ Fiddle — Conditions

🔒

Atelier réservé aux membres

📋 Écris une fonction noteVersMessage(note) qui retourne "Excellent" (≥90), "Bien" (≥70), "Passable" (≥50), ou "À revoir" sinon. Teste avec 95, 72, 55 et 30.

JavaScript
Résultat
💡

Pourquoi des boucles ?

Imaginez que vous devez envoyer un email de bienvenue à 500 nouveaux utilisateurs. Écrire 500 fois le même code serait absurde. Une boucle vous permet de répéter un bloc d'instructions automatiquement, un certain nombre de fois ou tant qu'une condition est vraie.

🔢

La boucle for — répéter un nombre défini de fois

La boucle for est la plus courante. Elle se compose de trois parties : l'initialisation (on crée un compteur), la condition (on s'arrête quand elle est fausse) et l'incrément (on fait avancer le compteur).

// for (initialisation ; condition ; incrément)
for (let i = 0; i < 5; i++) {
  console.log(`Tour numéro ${i}`);
}
// Affiche : Tour 0, Tour 1, Tour 2, Tour 3, Tour 4

// Compter à rebours
for (let i = 10; i > 0; i--) {
  console.log(i);
}
🔄

while — répéter tant qu'une condition est vraie

La boucle while est utilisée quand on ne sait pas à l'avance combien de fois on va répéter. Elle tourne tant que sa condition est vraie. Attention : si la condition ne devient jamais fausse, la boucle tourne à l'infini et bloque le programme.

let compteur = 0;
while (compteur < 3) {
  console.log(`Compteur : ${compteur}`);
  compteur++;             // sans ça, boucle infinie !
}
// Affiche : 0, 1, 2
📋

for...of et for...in — parcourir des collections

for...of parcourt les valeurs d'un tableau — c'est la façon moderne de parcourir une liste. for...in parcourt les clés d'un objet.

const fruits = ["pomme", "banane", "cerise"];

// for...of : valeurs du tableau
for (const fruit of fruits) {
  console.log(fruit); // pomme, banane, cerise
}

// for...in : clés d'un objet
const personne = { nom: "Alice", age: 25 };
for (const cle in personne) {
  console.log(`${cle} : ${personne[cle]}`);
}
⏹️

break et continue — contrôler une boucle

break arrête immédiatement la boucle. continue saute à l'itération suivante sans exécuter le reste du bloc.

// break : on s'arrête quand on trouve 5
for (let i = 0; i < 10; i++) {
  if (i === 5) break;
  console.log(i); // 0, 1, 2, 3, 4
}

// continue : on ignore le 3
for (let i = 0; i < 6; i++) {
  if (i === 3) continue;
  console.log(i); // 0, 1, 2, 4, 5
}
🎯

Atelier — Éditeur de code

✏️ Fiddle — Boucles

🔒

Atelier réservé aux membres

📋 Utilise une boucle for pour afficher la table de multiplication de 3 (de 1 à 10). Puis utilise for...of pour parcourir ["Python", "JavaScript", "CSS"] et afficher chaque technologie.

JavaScript
Résultat
💡

Qu'est-ce qu'un tableau ?

Une variable ne peut stocker qu'une seule valeur. Mais que faire si vous voulez stocker la liste des 50 produits d'un site e-commerce, ou les 200 utilisateurs d'une application ? C'est là qu'interviennent les tableaux (ou arrays).

Analogie : Un tableau, c'est comme une liste de courses. Chaque élément a une position (numérotée à partir de 0), et vous pouvez ajouter, supprimer ou modifier des éléments à volonté.
const fruits = ["pomme", "banane", "cerise"];

console.log(fruits[0]);      // "pomme"  (index 0)
console.log(fruits[2]);      // "cerise" (index 2)
console.log(fruits.length); // 3 (nombre d'éléments)
console.log(fruits.at(-1)); // "cerise" (dernier élément)

Ajouter et supprimer des éléments

JavaScript fournit plusieurs méthodes pour modifier un tableau. Les plus courantes agissent sur le début ou la fin du tableau :

const tab = ["a", "b", "c"];

tab.push("d");    // Ajoute à la fin   → ["a","b","c","d"]
tab.pop();        // Supprime la fin   → ["a","b","c"]
tab.unshift("z");// Ajoute au début   → ["z","a","b","c"]
tab.shift();      // Supprime le début → ["a","b","c"]

// splice(index, nbSupprimés, ...ajouts)
tab.splice(1, 1);          // Supprime 1 élément à l'index 1
tab.splice(1, 0, "X");    // Insère "X" à l'index 1
🔄

Transformer un tableau : map, filter, reduce

Ces trois méthodes sont fondamentales en JavaScript moderne. Elles ne modifient pas le tableau original — elles retournent un nouveau tableau (ou une valeur).

const prix = [10, 25, 5, 40, 15];

// map — transforme chaque élément
const prixTTC = prix.map(p => p * 1.20);
// [12, 30, 6, 48, 18]

// filter — garde les éléments qui passent le test
const chemin = prix.filter(p => p > 20);
// [25, 40]

// reduce — réduit le tableau à une seule valeur
const total = prix.reduce((somme, p) => somme + p, 0);
// 95
Ces méthodes se combinent : prix.filter(p => p > 10).map(p => p * 1.20) filtre d'abord, puis applique la TVA aux éléments filtrés.
🔍

Rechercher dans un tableau

JavaScript propose plusieurs méthodes pour trouver des éléments dans un tableau selon différents critères : vérifier une présence, trouver la valeur elle-même, ou trouver sa position. Choisir la bonne méthode rend le code plus lisible et plus efficace.

const nombres = [10, 20, 30, 20];

nombres.includes(20);          // true (existe ?)
nombres.indexOf(20);           // 1  (premier index trouvé)
nombres.find(n => n > 15);    // 20 (premier élément qui passe le test)
nombres.findIndex(n => n > 15);// 1  (son index)
nombres.every(n => n > 5);     // true (tous > 5 ?)
nombres.some(n => n > 25);     // true (au moins un > 25 ?)
🎯

Atelier — Éditeur de code

✏️ Fiddle — Tableaux

🔒

Atelier réservé aux membres

📋 Pars du tableau [12, 5, 8, 21, 3, 17, 9]. Utilise filter pour garder les nombres > 10, map pour les doubler, puis reduce pour calculer leur somme. Affiche chaque étape.

JavaScript
Résultat
💡

Qu'est-ce qu'une fonction ?

Une fonction est un bloc de code que vous définissez une fois et que vous pouvez exécuter autant de fois que vous le souhaitez, avec des données différentes à chaque fois. Sans les fonctions, vous seriez obligé de copier-coller le même code partout — et dès que vous voudriez le modifier, vous devriez le changer en dix endroits.

Analogie : Une fonction, c'est comme une recette de cuisine. Vous écrivez la recette une fois (vous la définissez), puis vous pouvez la suivre autant de fois que vous voulez (vous l'appelez), avec des ingrédients différents si besoin (les paramètres).
📝

Déclarer et appeler une fonction

Une fonction se déclare avec le mot-clé function, un nom, des paramètres entre parenthèses et un bloc d'instructions. Elle retourne une valeur avec return.

// Déclaration
function calculerTTC(prixHT, taux) {
  return prixHT * (1 + taux);
}

// Appels avec des données différentes
console.log(calculerTTC(100, 0.20)); // 120
console.log(calculerTTC(50, 0.10));  // 55
➡️

Arrow functions — la syntaxe moderne

Les arrow functions (fonctions fléchées) sont une façon plus courte d'écrire une fonction. Elles sont très utilisées en JavaScript moderne, notamment avec les méthodes de tableaux comme map ou filter.

// Fonction classique
function double(n) { return n * 2; }

// Arrow function équivalente
const double = (n) => n * 2;

// Avec paramètre par défaut
const saluer = (nom = "visiteur") => `Bonjour ${nom} !`;
console.log(saluer());          // "Bonjour visiteur !"
console.log(saluer("Alice"));  // "Bonjour Alice !"
🎒

Rest et Spread

...rest regroupe un nombre variable d'arguments dans un tableau. ...spread fait l'inverse : il étale un tableau en arguments individuels.

// Rest : accepte autant d'arguments qu'on veut
function somme(...nombres) {
  return nombres.reduce((acc, n) => acc + n, 0);
}
console.log(somme(1, 2, 3, 4)); // 10

// Spread : étale un tableau en arguments
const notes = [14, 17, 12, 19];
console.log(Math.max(...notes)); // 19

// Copier et fusionner des tableaux
const fusion = [...notes, 15, 16];
🎯

Atelier — Éditeur de code

✏️ Fiddle — Fonctions

🔒

Atelier réservé aux membres

📋 Écris une arrow function remise(prix, pct) qui retourne le prix après remise (ex: remise(100, 20) → 80). Écris aussi presentation(nom, age = 18) avec un paramètre par défaut. Teste les deux.

JavaScript
Résultat
💡

Qu'est-ce qu'un objet ?

Jusqu'ici, chaque variable stocke une seule valeur. Mais pour représenter une personne, il vous faut un nom, un âge, un email… Plutôt que de créer 10 variables séparées, on les regroupe dans un objet.

Analogie : Un objet, c'est comme une fiche d'identité. Elle contient plusieurs informations sur une même entité — nom, prénom, date de naissance — toutes organisées sous un même "dossier". En JavaScript, ces informations s'appellent des propriétés.
🏠

Créer un objet et accéder à ses propriétés

Un objet se crée avec des accolades {}. Chaque propriété est une paire clé: valeur. Un objet peut aussi contenir des méthodes — des fonctions qui lui appartiennent.

const utilisateur = {
  nom:    "Alice",
  age:    28,
  actif:  true,
  saluer() {
    return `Bonjour, je m'appelle ${this.nom} !`;
  }
};

// Accès par point (le plus courant)
console.log(utilisateur.nom);      // "Alice"
// Accès par crochets (utile si la clé est dynamique)
console.log(utilisateur["age"]);   // 28
console.log(utilisateur.saluer()); // "Bonjour, je m'appelle Alice !"
✏️

Modifier, ajouter, supprimer

Contrairement à une variable const primitive, un objet déclaré avec const reste modifiable : c'est la référence qui est fixe, pas son contenu. On peut donc ajouter, modifier ou supprimer des propriétés à tout moment.

const user = { nom: "Bob", age: 30 };

user.age = 31;                   // Modifier
user.email = "bob@mail.com";     // Ajouter
delete user.age;                  // Supprimer
console.log("nom" in user);      // true (existe ?)
📦

Déstructuration et spread

La déstructuration permet d'extraire des propriétés d'un objet directement dans des variables. C'est très pratique pour les paramètres de fonctions. Le spread (...) permet de copier ou fusionner des objets.

const personne = { prenom: "Alice", age: 28, ville: "Paris" };

// Déstructuration
const { prenom, ville } = personne;
console.log(prenom, ville); // "Alice" "Paris"

// Copier un objet (sans référence)
const copie = { ...personne };

// Fusionner deux objets
const extra    = { pays: "France" };
const complet  = { ...personne, ...extra };

// Explorer toutes les propriétés
console.log(Object.keys(personne));   // ["prenom","age","ville"]
console.log(Object.values(personne)); // ["Alice",28,"Paris"]
💡

Pourquoi les classes ?

Si vous devez créer 100 utilisateurs, vous n'allez pas écrire 100 objets à la main. Une classe est un modèle qui vous permet de fabriquer autant d'objets similaires que vous voulez. Chaque objet créé depuis une classe s'appelle une instance.

Analogie : Une classe, c'est le plan d'un architecte, ou le moule à gâteaux. Le plan lui-même n'est pas une maison — il permet d'en construire des dizaines, toutes avec la même structure de base mais des finitions différentes.
🏛️

Créer une classe

Le constructeur est une méthode spéciale appelée automatiquement quand on crée une instance avec new. Le mot-clé this désigne l'instance en cours de création.

class Utilisateur {
  constructor(nom, email) {
    this.nom   = nom;
    this.email = email;
    this.actif = true;
  }

  sePresenter() {
    return `Je suis ${this.nom}, contactez-moi : ${this.email}`;
  }

  desactiver() {
    this.actif = false;
  }
}

// Créer des instances
const alice = new Utilisateur("Alice", "alice@mail.com");
const bob   = new Utilisateur("Bob",   "bob@mail.com");

console.log(alice.sePresenter()); // "Je suis Alice..."
alice.desactiver();
console.log(alice.actif); // false
👶

L'héritage — extends et super

Une classe peut hériter d'une autre avec extends. La classe enfant récupère toutes les propriétés et méthodes de la classe parente, et peut en ajouter ou en modifier.

class Admin extends Utilisateur {
  constructor(nom, email, niveau) {
    super(nom, email); // appelle le constructeur parent
    this.niveau = niveau;
  }

  sePresenter() { // surcharge la méthode parente
    return `[Admin niv.${this.niveau}] ${this.nom}`;
  }
}

const admin = new Admin("Eve", "eve@mail.com", 3);
console.log(admin.sePresenter()); // "[Admin niv.3] Eve"
console.log(admin instanceof Utilisateur); // true
🔒

Membres statiques et champs privés

Un membre static appartient à la classe, pas aux instances. Un champ préfixé par # est privé : inaccessible depuis l'extérieur de la classe.

class Compteur {
  static total = 0;   // partagé par toutes les instances
  #valeur = 0;        // privé : inaccessible hors de la classe

  incrementer() {
    this.#valeur++;
    Compteur.total++;
  }

  get valeur() { return this.#valeur; }
}

const c1 = new Compteur();
c1.incrementer();
console.log(c1.valeur);        // 1
console.log(Compteur.total);   // 1
// console.log(c1.#valeur);    // SyntaxError : champ privé
🔤

Manipuler les chaînes de texte

Les strings sont immuables : ces méthodes ne modifient pas la chaîne originale, elles retournent une nouvelle chaîne.

const texte = "  Bonjour le monde  ";

texte.trim();                      // "Bonjour le monde"  (supprime espaces)
texte.toLowerCase();               // "  bonjour le monde  "
texte.toUpperCase();               // "  BONJOUR LE MONDE  "
texte.includes("monde");          // true
texte.startsWith("  B");          // true
texte.replace("monde", "monde !");// "  Bonjour le monde !  "
texte.split(" ");                 // découpe en tableau de mots
texte.slice(2, 9);               // "Bonjour" (extrait)
"ID".padStart(6, "0");           // "0000ID"  (compléter)
🔢

Mathématiques et nombres

L'objet Math fournit toutes les opérations mathématiques courantes : arrondi, valeur absolue, minimum, maximum, aléatoire… Les fonctions parseInt et parseFloat convertissent des chaînes de texte en nombres — indispensable quand on lit des valeurs saisies par l'utilisateur (qui sont toujours des strings).

// Arrondir
Math.round(4.6);      // 5  (arrondi classique)
Math.floor(4.9);      // 4  (arrondi vers le bas)
Math.ceil(4.1);       // 5  (arrondi vers le haut)
(3.14159).toFixed(2);// "3.14" (string avec 2 décimales)

// Utilitaires
Math.abs(-10);         // 10  (valeur absolue)
Math.max(3, 1, 4);    // 4   (maximum)
Math.min(3, 1, 4);    // 1   (minimum)
Math.random();         // 0.0 à 0.999 (aléatoire)
Math.sqrt(16);         // 4   (racine carrée)

// Conversion
parseInt("42px");      // 42      (string → entier)
parseFloat("3.14abc");// 3.14   (string → décimal)
Number.isInteger(42); // true
Number.isNaN(NaN);     // true
📦

JSON — échanger des données

Le format JSON (JavaScript Object Notation) est le standard pour échanger des données entre un serveur et un client web. On convertit un objet en texte JSON avec JSON.stringify, et on fait l'inverse avec JSON.parse.

const utilisateur = { nom: "Alice", age: 28 };

// Objet → chaîne JSON (pour l'envoyer à un serveur)
const json = JSON.stringify(utilisateur);
// '{"nom":"Alice","age":28}'

// Chaîne JSON → Objet (quand on reçoit une réponse)
const retour = JSON.parse(json);
console.log(retour.nom); // "Alice"
📅

Les dates

L'objet Date représente un instant précis dans le temps. Il est utilisé pour afficher la date du jour, calculer une durée, trier des événements par ordre chronologique. Attention : les mois commencent à 0 (janvier = 0), c'est une source d'erreur classique.

const maintenant = new Date();

maintenant.getFullYear();               // 2026
maintenant.getMonth();                  // 8 (attention : 0 = janvier !)
maintenant.getDate();                   // 4 (jour du mois)
maintenant.getDay();                    // 5 (0=dim, 1=lun... 5=ven)
maintenant.toLocaleDateString("fr-FR"); // "04/09/2026"
maintenant.toLocaleTimeString("fr-FR"); // "14:30:00"

// Date précise
const noel = new Date(2026, 11, 25); // 25 décembre 2026
Attention : getMonth() retourne 0 pour janvier, 1 pour février… et 11 pour décembre. C'est une des sources d'erreur les plus classiques avec les dates en JavaScript.
🎯

Atelier — Éditeur de code

✏️ Fiddle — Méthodes utiles

🔒

Atelier réservé aux membres

📋 À partir de const phrase = "apprendre javascript est passionnant" : affiche-la en majuscules, remplace "passionnant" par "génial", et affiche son nombre de caractères. Puis trie et affiche ["Zoé", "Alice", "Marc", "Baptiste"].

JavaScript
Résultat
🤔

Le problème que K8s résout

Avec Docker seul, tu gères tes conteneurs à la main : démarrer, arrêter, redémarrer en cas de crash, router le trafic… C'est gérable pour 3 conteneurs. Impossible pour 300.

Problème en productionSolution Kubernetes
Un conteneur crashe → service downK8s le redémarre automatiquement
Trop de trafic → surchargeK8s scale horizontalement (plus de réplicas)
Mise à jour → downtimeRolling update : remplace les pods un par un
Plusieurs serveurs à gérerK8s abstrait le cluster comme une seule machine
🏗️

Architecture d'un cluster

Cluster Kubernetes
├── Control Plane (cerveau du cluster)
│   ├── API Server     — point d'entrée de toutes les commandes
│   ├── etcd           — base de données clé-valeur (état du cluster)
│   ├── Scheduler      — décide sur quel Node placer chaque Pod
│   └── Controller     — surveille et corrige l'état réel vs désiré
│
└── Nodes (machines qui font tourner les apps)
    ├── kubelet        — agent sur chaque Node, exécute les Pods
    ├── kube-proxy     — gère le réseau entre Pods
    └── Pods           — un ou plusieurs conteneurs Docker
kubectl est l'outil en ligne de commande pour interagir avec le cluster. Toutes tes commandes passent par lui : kubectl get pods, kubectl apply -f, kubectl logs
⚙️

Installer un cluster local

Pour apprendre et développer en local, deux options populaires :

OutilUsageCommande de départ
minikubeCluster K8s local en une VM ou containerminikube start
kindK8s dans Docker — très légerkind create cluster
Docker DesktopK8s intégré, activable en un clicSettings → Kubernetes → Enable
📦

Créer et gérer des Pods

# Lancer un pod de test (nginx)
kubectl run nginx --image=nginx --port=80

# Lister les pods
kubectl get pods

# Voir les détails d'un pod
kubectl describe pod nginx

# Voir les logs
kubectl logs nginx

# Supprimer
kubectl delete pod nginx
Ne crée jamais un Pod directement en production. Les pods isolés ne sont pas redémarrés en cas de crash. Utilise toujours un Deployment.
🔄

Deployment — la vraie façon

Un Deployment déclare l'état désiré : "je veux 3 réplicas de cette image". Kubernetes s'assure que cet état est toujours respecté.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: mon-app
spec:
  replicas: 3                    # 3 instances en parallèle
  selector:
    matchLabels:
      app: mon-app
  template:
    metadata:
      labels:
        app: mon-app
    spec:
      containers:
      - name: mon-app
        image: monimage:1.0
        ports:
        - containerPort: 3000
        resources:
          requests:
            memory: "64Mi"
            cpu: "250m"
          limits:
            memory: "128Mi"
            cpu: "500m"
# Appliquer le fichier YAML
kubectl apply -f deployment.yaml

# Voir l'état du déploiement
kubectl rollout status deployment/mon-app

# Mettre à jour l'image (rolling update)
kubectl set image deployment/mon-app mon-app=monimage:2.0

# Annuler la dernière mise à jour
kubectl rollout undo deployment/mon-app
🔌

Types de Services

TypeAccèsUsage
ClusterIPInterne au cluster uniquementCommunication entre microservices
NodePortAccessible via IP du Node + portTests, dev local
LoadBalancerIP publique via load balancer cloudProduction sur AWS/GCP/Azure
IngressRoutage HTTP/HTTPS par hostname/pathExposition multi-services en prod
apiVersion: v1
kind: Service
metadata:
  name: mon-app-svc
spec:
  selector:
    app: mon-app          # cible les Pods avec ce label
  ports:
  - port: 80
    targetPort: 3000
  type: ClusterIP
🚦

Ingress — routage HTTP

L'Ingress permet d'exposer plusieurs services derrière une seule IP publique, en routant par hostname ou chemin URL — comme un reverse proxy Nginx piloté par K8s.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: mon-ingress
spec:
  rules:
  - host: app.mondomaine.com
    http:
      paths:
      - path: /api
        pathType: Prefix
        backend:
          service:
            name: api-svc
            port:
              number: 80
      - path: /
        pathType: Prefix
        backend:
          service:
            name: frontend-svc
            port:
              number: 80
📋

ConfigMap

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  APP_ENV: production
  APP_PORT: "3000"
  DB_HOST: mysql-svc
# Injecter en variables d'environnement dans un Pod
spec:
  containers:
  - name: mon-app
    envFrom:
    - configMapRef:
        name: app-config
🔑

Secret

Les Secrets sont encodés en base64 (pas chiffrés par défaut — active Encryption at Rest ou utilise un outil externe comme Vault pour la vraie sécurité).

# Créer un secret depuis la ligne de commande
kubectl create secret generic db-credentials \
  --from-literal=DB_USER=admin \
  --from-literal=DB_PASSWORD=monmotdepasse

# L'injecter dans le Pod
spec:
  containers:
  - name: mon-app
    envFrom:
    - secretRef:
        name: db-credentials
Ne commite jamais un Secret YAML en clair dans Git. Utilise des outils comme Sealed Secrets, External Secrets Operator, ou Vault pour gérer les secrets K8s de façon sécurisée.
💡

Pourquoi Helm ?

Déployer une application comme Nginx, Prometheus ou une base de données sur K8s nécessite souvent 5 à 10 fichiers YAML. Helm les regroupe dans un chart, avec des valeurs paramétrables.

# Ajouter un dépôt de charts
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update

# Installer une application (ex: MySQL)
helm install ma-db bitnami/mysql \
  --set auth.rootPassword=secret \
  --set auth.database=monapp

# Lister les releases installées
helm list

# Mettre à jour
helm upgrade ma-db bitnami/mysql --set auth.rootPassword=newsecret

# Désinstaller
helm uninstall ma-db
📁

Structure d'un chart

mon-chart/
├── Chart.yaml          # métadonnées (nom, version, description)
├── values.yaml         # valeurs par défaut paramétrables
└── templates/
    ├── deployment.yaml # templates YAML avec variables {{ .Values.xxx }}
    ├── service.yaml
    └── ingress.yaml
# Créer un chart vide
helm create mon-chart

# Installer depuis un dossier local
helm install mon-app ./mon-chart -f mes-valeurs.yaml

# Prévisualiser le YAML généré sans appliquer
helm template mon-app ./mon-chart
🤔

Pourquoi l'IaC ?

Sans IaC (clic manuel)Avec IaC (Terraform)
Impossible à reproduire exactementInfrastructure identique à chaque déploiement
Pas de trace de qui a changé quoiHistorique complet dans Git
Créer un environnement de staging = refaire toutterraform apply = nouvel env identique
Erreur humaine = panneCode reviewé, testé, versionné
🗺️

Les outils IaC du marché

OutilÉditeurLangagePoint fort
TerraformHashiCorpHCLMulti-cloud, écosystème énorme
OpenTofuOpen source (fork Terraform)HCLIdentique à Terraform, vraiment open source
PulumiPulumi CorpPython, JS, Go…Vrai langage de programmation
AWS CDKAmazonTypeScript, PythonNatif AWS, très puissant sur AWS
AnsibleRed HatYAMLConfig management + provisioning léger
Terraform reste la référence multi-cloud. OpenTofu est son fork open source et est compatible. Ce cours s'applique aux deux.
🔄

Le workflow Terraform

terraform init      # télécharge les providers et initialise le backend
terraform plan      # prévisualise les changements (diff infra)
terraform apply     # applique les changements (crée/modifie/détruit)
terraform destroy   # détruit toute l'infrastructure
Toujours lire le plan avant d'appliquer. terraform plan affiche exactement ce qui va être créé, modifié ou détruit — c'est le git diff de ton infrastructure.
📝

Syntaxe HCL — premier exemple

# main.tf — créer une instance EC2 sur AWS

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = "eu-west-3"   # Paris
}

resource "aws_instance" "mon_serveur" {
  ami           = "ami-0c55b159cbfafe1f0"  # Ubuntu 22.04
  instance_type = "t3.micro"

  tags = {
    Name = "DevAcademia-Server"
    Env  = "production"
  }
}
📦

Variables et outputs

# variables.tf
variable "instance_type" {
  description = "Type d'instance EC2"
  type        = string
  default     = "t3.micro"
}

variable "environment" {
  description = "Environnement (dev/staging/prod)"
  type        = string
}

# outputs.tf — valeurs exposées après apply
output "ip_publique" {
  description = "IP publique du serveur"
  value       = aws_instance.mon_serveur.public_ip
}
# Passer une variable en ligne de commande
terraform apply -var="environment=production"

# Ou via un fichier terraform.tfvars
environment = "production"
instance_type = "t3.small"
🗂️

Providers populaires

ProviderGèreSource
awsAmazon Web Services — EC2, S3, RDS, VPC…hashicorp/aws
googleGoogle Cloud Platformhashicorp/google
azurermMicrosoft Azurehashicorp/azurerm
kubernetesRessources K8s (Deployments, Services…)hashicorp/kubernetes
dockerImages et containers Docker locauxkreuzwerker/docker
githubRepos, teams, webhooks GitHubintegrations/github
🔗

Référencer des ressources

Les resources peuvent se référencer entre elles. Terraform construit automatiquement un graphe de dépendances et les crée dans le bon ordre.

# Créer un VPC, puis un subnet dans ce VPC
resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"
}

resource "aws_subnet" "public" {
  vpc_id     = aws_vpc.main.id     # référence le VPC ci-dessus
  cidr_block = "10.0.1.0/24"
  availability_zone = "eu-west-3a"
}

resource "aws_security_group" "web" {
  vpc_id = aws_vpc.main.id         # même VPC

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
}
⚠️

Pourquoi ne pas garder le state en local

  • Si ton collègue fait terraform apply avec un state différent du tien → conflit et infrastructure corrompue
  • Le state contient des données sensibles (mots de passe, clés) en clair
  • Si tu perds le fichier, Terraform ne sait plus ce qu'il gère
Ne commite jamais terraform.tfstate dans Git. Ajoute-le à ton .gitignore.
☁️

Remote backend — S3 + DynamoDB (AWS)

terraform {
  backend "s3" {
    bucket         = "mon-bucket-tfstate"
    key            = "prod/terraform.tfstate"
    region         = "eu-west-3"
    encrypt        = true

    # DynamoDB pour verrouiller le state (évite les apply simultanés)
    dynamodb_table = "terraform-lock"
  }
}

Autres backends populaires : Terraform Cloud (gratuit jusqu'à 5 users), GitLab, Google Cloud Storage, Azure Blob Storage.

📁

Structure d'un module

modules/
└── vpc/
    ├── main.tf        # ressources du module
    ├── variables.tf   # inputs du module
    └── outputs.tf     # valeurs exposées

# Appel du module depuis la racine
module "mon_vpc" {
  source = "./modules/vpc"

  cidr_block  = "10.0.0.0/16"
  environment = "production"
  region      = "eu-west-3"
}
📋

Bonnes pratiques

PratiquePourquoi
Un dossier par environnement (dev/, prod/)Évite d'appliquer du code de dev en prod
Remote backend dès le débutPas de "oups j'ai écrasé le state"
terraform fmt avant tout commitCode HCL formaté et lisible
terraform validate en CIDétecte les erreurs de syntaxe avant apply
Tagguer toutes les ressourcesSavoir à quoi appartient chaque ressource cloud
🤔

Ansible vs Terraform

Les deux font de l'automatisation, mais à des niveaux différents :

TerraformAnsible
Quoi ?Provisionne l'infrastructure (crée les serveurs)Configure l'infrastructure (installe les logiciels)
Quand ?Avant que les serveurs existentAprès que les serveurs existent
StyleDéclaratif — décrit l'état finalProcédural — décrit les étapes
Agent ?NonNon (se connecte en SSH)
En pratique, les deux se complètent : Terraform crée le serveur, Ansible le configure. Beaucoup d'équipes utilisent les deux ensemble.
📦

Installation et premier test

# Installer Ansible (Linux/Mac)
pip3 install ansible

# Tester la connexion à un hôte
ansible all -i "192.168.1.10," -m ping --user ubuntu

# Exécuter une commande ad-hoc sur tous les serveurs
ansible all -i inventaire.ini -m shell -a "uptime"
ansible all -i inventaire.ini -m apt -a "name=nginx state=present" --become
📝

Structure d'un playbook

# setup-webserver.yml
---
- name: Configurer un serveur web Nginx
  hosts: webservers          # groupe d'hôtes de l'inventaire
  become: true               # sudo

  vars:
    app_port: 80
    doc_root: /var/www/html

  tasks:
    - name: Mettre à jour les paquets
      apt:
        update_cache: yes

    - name: Installer Nginx
      apt:
        name: nginx
        state: present

    - name: Copier la configuration Nginx
      template:
        src: nginx.conf.j2
        dest: /etc/nginx/nginx.conf
      notify: Redémarrer Nginx    # déclenche le handler

    - name: Démarrer et activer Nginx
      service:
        name: nginx
        state: started
        enabled: yes

  handlers:
    - name: Redémarrer Nginx
      service:
        name: nginx
        state: restarted
# Exécuter le playbook
ansible-playbook -i inventaire.ini setup-webserver.yml

# Mode dry-run : voir ce qui serait fait sans l'appliquer
ansible-playbook -i inventaire.ini setup-webserver.yml --check
📦

Modules Ansible courants

ModuleUsage
apt / yumInstaller/supprimer des paquets
copy / templateCopier des fichiers (template = avec variables Jinja2)
service / systemdDémarrer/arrêter/activer des services
user / groupGérer les utilisateurs et groupes
fileCréer/supprimer fichiers et dossiers, permissions
gitCloner/mettre à jour un dépôt Git
docker_containerGérer des containers Docker
📋

Inventaire INI et YAML

# inventaire.ini
[webservers]
web1 ansible_host=192.168.1.10 ansible_user=ubuntu
web2 ansible_host=192.168.1.11 ansible_user=ubuntu

[databases]
db1 ansible_host=192.168.1.20 ansible_user=ubuntu

[prod:children]    # groupe de groupes
webservers
databases

[prod:vars]        # variables pour tout le groupe prod
ansible_ssh_private_key_file=~/.ssh/prod_key
📦

Variables — priorité et organisation

Ansible a une hiérarchie de priorité pour les variables. Les plus spécifiques écrasent les plus générales.

group_vars/
├── all.yml          # variables pour tous les hôtes
├── webservers.yml   # variables pour le groupe webservers
└── prod.yml         # variables pour le groupe prod

host_vars/
├── web1.yml         # variables spécifiques à web1
└── db1.yml
# group_vars/webservers.yml
nginx_port: 80
app_user: www-data
deploy_dir: /var/www/html
Les variables sensibles (mots de passe) ne doivent pas être en clair dans les fichiers. Utilise Ansible Vault : ansible-vault encrypt group_vars/prod.yml.
📁

Structure d'un rôle

roles/nginx/
├── tasks/
│   └── main.yml      # tâches principales
├── handlers/
│   └── main.yml      # handlers (redémarrage de service…)
├── templates/
│   └── nginx.conf.j2 # templates Jinja2
├── files/
│   └── index.html    # fichiers statiques à copier
├── vars/
│   └── main.yml      # variables internes au rôle
└── defaults/
    └── main.yml      # valeurs par défaut (priorité basse)
# Créer la structure automatiquement
ansible-galaxy role init nginx

# Utiliser un rôle dans un playbook
- hosts: webservers
  roles:
    - nginx
    - { role: mysql, mysql_root_password: secret }
🌐

Ansible Galaxy

Galaxy est le dépôt public de rôles Ansible. Des milliers de rôles maintenus par la communauté pour les usages les plus courants.

# Installer un rôle depuis Galaxy (ex: Nginx de geerlingguy)
ansible-galaxy role install geerlingguy.nginx

# Déclarer les dépendances dans requirements.yml
roles:
  - name: geerlingguy.nginx
    version: "3.2.0"
  - name: geerlingguy.mysql
    version: "4.3.0"

# Installer toutes les dépendances d'un coup
ansible-galaxy install -r requirements.yml
🏛️

Logs, Métriques, Traces

PilierC'est quoiQuestion à laquelle ça répondOutils
📜 Logs Événements horodatés en texte — ce qui s'est passé "Quelle erreur s'est produite à 14h37 ?" ELK Stack, Loki, Splunk, CloudWatch
📈 Métriques Mesures numériques dans le temps — CPU, RAM, latence, requêtes/s "Est-ce que ça ralentit depuis 30 min ?" Prometheus, Datadog, InfluxDB
🔍 Traces Suivi d'une requête de bout en bout à travers les services "Quel microservice a causé le délai de 2s ?" Jaeger, Zipkin, Tempo
Les trois sont complémentaires. Une alerte métrique te dit qu'il y a un problème. Les logs te disent ce qui s'est passé. Les traces te disent exactement dans le parcours de la requête.
🗺️

La stack classique

Application → Prometheus (collecte métriques)
                    │
                    ▼
              Grafana (visualisation + alertes)

Application → Loki (collecte logs)
                    │
                    ▼
              Grafana (même outil, onglet Logs)

Application → OpenTelemetry (traces)
                    │
                    ▼
              Tempo / Jaeger (visualisation)

En 2024, la stack Prometheus + Grafana + Loki + Tempo (toute Grafana Labs) est la référence open source pour l'observabilité complète.

🔄

Comment Prometheus fonctionne

Application          Prometheus          Grafana
(expose /metrics) ←─ scrape toutes ──→ (visualise)
                       15 secondes

L'application expose un endpoint /metrics qui retourne des métriques au format texte. Prometheus vient régulièrement les lire (pull model).

# Exemple de métriques exposées par une app Node.js
# HELP http_requests_total Nombre de requêtes HTTP
# TYPE http_requests_total counter
http_requests_total{method="GET",status="200"} 1847
http_requests_total{method="POST",status="201"} 234
http_requests_total{method="GET",status="500"} 12

# HELP process_memory_bytes Mémoire utilisée
# TYPE process_memory_bytes gauge
process_memory_bytes 45678592
🔍

PromQL — requêter les métriques

Requête PromQLCe qu'elle donne
http_requests_totalToutes les valeurs de ce compteur
rate(http_requests_total[5m])Taux de requêtes par seconde sur 5 min
sum(rate(http_requests_total[5m])) by (status)Taux groupé par code HTTP
histogram_quantile(0.99, http_duration_seconds_bucket)Latence au 99e percentile
process_memory_bytes / 1024 / 1024Mémoire en Mo
🚀

Démarrer en local avec Docker

# docker-compose.yml — stack Prometheus + Grafana
services:
  prometheus:
    image: prom/prometheus:latest
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
    ports:
      - "9090:9090"

  grafana:
    image: grafana/grafana:latest
    ports:
      - "3001:3000"
    environment:
      GF_SECURITY_ADMIN_PASSWORD: admin
    volumes:
      - grafana-data:/var/lib/grafana

volumes:
  grafana-data:

Accède à Grafana sur http://localhost:3001 (admin/admin), ajoute Prometheus comme datasource (http://prometheus:9090), puis crée ton premier dashboard.

🔔

Alertes

Grafana peut envoyer des alertes quand une métrique dépasse un seuil — par email, Slack, PagerDuty, webhook…

# Exemple de règle d'alerte (via l'interface ou YAML)
Condition : avg(rate(http_requests_total{status="500"}[5m])) > 0.1
Pendant   : 5 minutes
Envoyer   : Slack #alertes-prod
Message   : "Taux d'erreurs 5xx élevé : {{ $value }} req/s"
Seuils d'alerte à régler avec soin. Trop sensibles = alert fatigue (on les ignore). Pas assez sensibles = incident non détecté. Commence par alerter sur les SLOs (erreurs, latence) plutôt que sur les métriques systèmes.
📦

ELK Stack vs Loki

ELK StackGrafana Loki
ComposantsElasticsearch + Logstash + KibanaLoki + Promtail + Grafana
StockageIndexe tout le contenu des logsN'indexe que les labels (plus léger)
RessourcesLourd — nécessite beaucoup de RAMLéger — conçu pour K8s
RequêtesDSL ElasticsearchLogQL (proche PromQL)
Idéal pourVolumes très importants, analyse complexeStack Grafana unifiée, K8s
🔍

LogQL — requêter les logs avec Loki

# Tous les logs du conteneur nginx
{container="nginx"}

# Logs contenant "error" sur les dernières 15 min
{container="nginx"} |= "error"

# Compter les erreurs par minute
rate({container="nginx"} |= "error" [1m])

# Extraire un champ et filtrer
{app="mon-app"} | json | status_code >= 500
💡

CI vs CD — quelle différence ?

SigleSignifieCe que ça fait
CI Continuous Integration À chaque push : compile, lance les tests, vérifie la qualité du code. Détecte les régressions immédiatement.
CD (Delivery) Continuous Delivery Le code est automatiquement prêt à déployer en prod après validation — mais le déploiement est manuel.
CD (Deployment) Continuous Deployment Tout est automatique : un commit en main = déploiement en prod sans intervention humaine.
🔄

Un pipeline typique

git push
    │
    ▼
[1. Build]
   Compiler le code / builder l'image Docker
    │
    ▼
[2. Test]
   Tests unitaires → tests d'intégration → tests E2E
    │
    ▼
[3. Analyse qualité]
   Linter, couverture de code, scan de sécurité
    │
    ▼
[4. Build artefact]
   Créer l'image Docker, la pusher sur le registry
    │
    ▼
[5. Déploiement staging]
   Déployer automatiquement sur l'env de staging
    │
    ▼
[6. Déploiement prod]
   Manuel (Delivery) ou automatique (Deployment)
📁

Structure d'un workflow

Les workflows sont des fichiers YAML dans .github/workflows/. GitHub les détecte et les exécute automatiquement.

# .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest        # machine virtuelle GitHub

    steps:
      - name: Checkout du code
        uses: actions/checkout@v4

      - name: Installer Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Installer les dépendances
        run: npm ci

      - name: Lancer les tests
        run: npm test

      - name: Vérifier le formatage
        run: npm run lint
🔑

Secrets et variables

Les informations sensibles (tokens, mots de passe) se stockent dans Settings → Secrets du dépôt GitHub, jamais dans le code.

# Utiliser un secret dans un workflow
- name: Login sur Docker Hub
  uses: docker/login-action@v3
  with:
    username: ${{ secrets.DOCKERHUB_USERNAME }}
    password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Déployer sur le serveur
  run: |
    ssh -i "${{ secrets.SSH_PRIVATE_KEY }}" \
        user@${{ secrets.SERVER_IP }} \
        "cd /app && docker compose pull && docker compose up -d"
📋

Pipeline multi-jobs

name: CI/CD Pipeline

on:
  push:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: '20' }
      - run: npm ci
      - run: npm test

  build:
    needs: test          # attend que "test" réussisse
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: docker/setup-buildx-action@v3
      - uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}
      - uses: docker/build-push-action@v5
        with:
          push: true
          tags: monuser/monapp:${{ github.sha }}

  deploy:
    needs: build         # attend que "build" réussisse
    runs-on: ubuntu-latest
    environment: production    # protection manuelle possible
    steps:
      - name: Déployer via SSH
        run: |
          echo "${{ secrets.SSH_KEY }}" > key.pem
          chmod 600 key.pem
          ssh -i key.pem user@${{ secrets.SERVER_IP }} \
            "docker pull monuser/monapp:${{ github.sha }} && \
             docker stop monapp || true && \
             docker run -d --name monapp -p 3000:3000 \
               monuser/monapp:${{ github.sha }}"
🏗️

Dockerfile optimisé pour CI

Un Dockerfile multi-stage réduit la taille de l'image finale en séparant l'étape de build de l'étape d'exécution.

# Stage 1 : builder (contient node_modules de dev, outils de build)
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci                    # install avec lock file exact
COPY . .
RUN npm run build             # compile TypeScript, bundle…

# Stage 2 : image finale (légère, sans outils de dev)
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production

COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package*.json ./
RUN npm ci --omit=dev         # seulement les dépendances de prod

EXPOSE 3000
USER node                     # pas de root en prod
CMD ["node", "dist/server.js"]
🏷️

Stratégie de tags d'images

TagExempleUsage
Hash du commitmonapp:a3f5b1cTraçabilité exacte — recommandé en prod
Numéro de versionmonapp:1.4.2Releases stables avec semantic versioning
Nom de branchemonapp:developEnvironnements de staging par branche
latestmonapp:latestÀ éviter en prod — on ne sait pas exactement quoi est déployé
Signe tes images avec cosign (Sigstore) pour garantir l'intégrité et que personne n'a modifié l'image entre le build CI et le déploiement prod.
🔄

Client vs serveur — deux mondes différents

Quand tu ouvres un site, deux ordinateurs travaillent : ton navigateur (le client) et la machine distante (le serveur). Ils ont des rôles très différents.

Client (navigateur)Serveur (Node.js)
Exécute JavaScript côté utilisateurExécute JavaScript côté machine distante
Accès au DOM, aux événements, au CSSAccès aux fichiers, à la DB, au réseau
Chaque utilisateur a son propre contexteUn seul processus répond à tous les utilisateurs
Pas de persistance — les données disparaissent au rechargementPersistance totale — données stockées en base
Code visible par l'utilisateur (DevTools)Code secret, jamais envoyé au client
Le JavaScript a été inventé en 1995 pour les navigateurs. C'est en 2009 que Ryan Dahl a créé Node.js, permettant pour la première fois d'exécuter du JS hors du navigateur — sur un serveur, en ligne de commande, ou partout ailleurs.

Pourquoi Node.js ?

Node.js est un runtime JavaScript basé sur le moteur V8 de Chrome. Il excelle dans un cas précis : gérer un grand nombre de connexions simultanées sans bloquer le processus.

Sa force vient de son modèle non-bloquant : plutôt que d'attendre qu'une opération lente (lecture de fichier, requête DB) soit terminée avant de passer à la suivante, Node.js continue d'autres tâches pendant l'attente, puis exécute le callback quand le résultat arrive.

Modèle bloquant (PHP, Python sync)Modèle non-bloquant (Node.js)
Requête DB lenteLe thread attend — bloquéNode continue d'autres requêtes
1000 connexions simultanées1000 threads en mémoire1 seul thread, event loop
Idéal pourCalculs CPU intensifsAPIs, temps réel, microservices
npm (Node Package Manager) est livré avec Node.js. Il donne accès à plus de 2 millions de packages — Express, SQLite, bcrypt, JWT, etc. C'est l'écosystème le plus grand de tous les langages.
🚀

Ton premier serveur HTTP

Node.js intègre nativement un module http qui permet de créer un serveur web sans installer quoi que ce soit.

// serveur.js
const http = require('http');

const serveur = http.createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
  res.end('Bonjour depuis Node.js !');
});

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

Pour le lancer :

node serveur.js

Ouvre http://localhost:3000 dans ton navigateur — tu vois le message. Chaque requête HTTP déclenche la fonction callback avec deux objets : req (la requête entrante — URL, méthode, headers, body) et res (la réponse que tu vas envoyer).

Port 3000 est la convention pour le développement Node.js. En production, le serveur écoute sur le port 80 (HTTP) ou 443 (HTTPS), mais un reverse proxy comme Nginx redirige vers ton port interne.
📦

Express.js — le framework web

Le module http natif est bas niveau — gérer les routes, parser le body JSON, renvoyer des erreurs devient vite verbeux. Express.js est le framework le plus utilisé dans l'écosystème Node pour simplifier tout ça.

npm init -y            # crée package.json
npm install express   # installe Express
// app.js
const express = require('express');
const app = express();

app.use(express.json());  // parse automatiquement le body JSON

// Route GET sur "/"
app.get('/', (req, res) => {
  res.json({ message: 'Bonjour !' });
});

// Route GET avec paramètre dynamique
app.get('/utilisateurs/:id', (req, res) => {
  const { id } = req.params;   // "/utilisateurs/42" → id = "42"
  res.json({ id, nom: 'Alice' });
});

app.listen(3000, () => console.log('Serveur Express sur http://localhost:3000'));
Si tu appelles GET /utilisateurs/42, Express extrait 42 depuis l'URL via req.params.id. Si tu passes ?page=2, c'est dans req.query.page. Le body d'un POST est dans req.body (grâce à express.json()).
🗂️

Structure d'un projet Node.js

Un projet Express minimal ressemble à ça :

mon-projet/
├── package.json       # métadonnées + dépendances
├── package-lock.json  # versions exactes (ne pas toucher)
├── node_modules/      # packages installés (ne jamais committer)
├── app.js             # point d'entrée principal
├── db.js              # connexion + init base de données
└── .gitignore         # contient "node_modules/"

Le fichier package.json liste les dépendances. Quand tu clones un projet, un simple npm install recrée tout le dossier node_modules automatiquement.

Ne commite jamais node_modules/ dans Git — ce dossier peut peser des centaines de Mo et se recrée en une commande. Ajoute-le toujours dans .gitignore.
💾

Pourquoi stocker en base ?

La mémoire vive (RAM) est rapide mais éphémère. Une base de données écrit sur le disque dur — les données survivent aux redémarrages, aux pannes, et sont accessibles depuis plusieurs processus ou serveurs en même temps.

Variable en mémoireBase de données
Survit au redémarrage❌ Non✅ Oui
Accessible depuis plusieurs serveurs❌ Non✅ Oui
Requêtes complexes (filtre, tri, jointure)❌ Manuel✅ SQL
Transactions (tout ou rien)❌ Non✅ Oui
Vitesse d'accès⚡ ImmédiateQuelques ms
🗃️

SQL vs NoSQL — choisir la bonne base

Il existe deux grandes familles de bases de données, avec des philosophies opposées.

Bases SQL (relationnelles)Bases NoSQL
ExemplesMySQL, PostgreSQL, SQLiteMongoDB, Redis, DynamoDB
StructureTables avec colonnes fixesDocuments JSON flexibles
RelationsJointures entre tablesDocuments imbriqués
Idéal pourDonnées structurées, intégrité forteDonnées variables, scale horizontal
Pour la grande majorité des projets web — applications, APIs, e-commerce — une base SQL comme PostgreSQL ou MySQL est le bon choix. MongoDB est souvent présenté comme "plus simple pour débuter", mais la structuration SQL force à mieux modéliser ses données dès le départ.
🔹

SQLite — la base parfaite pour débuter

SQLite est une base SQL particulière : elle stocke tout dans un seul fichier .db sur le disque, sans nécessiter de serveur séparé. Parfaite pour apprendre, prototyper, ou des projets à faible concurrence.

SQLiteMySQL / PostgreSQL
InstallationAucune — juste un fichierServeur à installer et configurer
Connexionnew Database('fichier.db')Host, port, user, password, db
UsageDev, tests, apps légèresProduction, multi-utilisateurs
SQL supportéQuasi-completComplet + extensions
Le SQL que tu apprends avec SQLite fonctionne à 95% pareil sur MySQL et PostgreSQL. Maîtriser SQLite te donne une base solide pour passer ensuite à une vraie base de production.

On utilisera le package better-sqlite3 — une API synchrone, claire et très performante :

npm install better-sqlite3
🔌

Se connecter et créer une table

Voici comment initialiser une base SQLite et créer une table si elle n'existe pas encore :

// db.js
const Database = require('better-sqlite3');

const db = new Database('mabase.db');  // crée le fichier s'il n'existe pas

// Activer les clés étrangères (bonne pratique)
db.pragma('foreign_keys = ON');

// Créer la table "utilisateurs" si elle n'existe pas
db.exec(`
  CREATE TABLE IF NOT EXISTS utilisateurs (
    id        INTEGER PRIMARY KEY AUTOINCREMENT,
    nom       TEXT NOT NULL,
    email     TEXT NOT NULL UNIQUE,
    cree_le   TEXT DEFAULT CURRENT_TIMESTAMP
  )
`);

console.log('Base de données prête');

module.exports = db;
AUTOINCREMENT — SQLite attribue automatiquement un ID unique à chaque nouvelle ligne. NOT NULL — la colonne est obligatoire. UNIQUE — deux lignes ne peuvent pas avoir le même email. DEFAULT CURRENT_TIMESTAMP — la date est remplie automatiquement à l'insertion.
🔍

Les 4 opérations SQL fondamentales

Toutes les interactions avec une base se résument à quatre opérations — regroupées sous l'acronyme CRUD : Create, Read, Update, Delete.

const db = require('./db');

// CREATE — insérer une ligne
const insert = db.prepare('INSERT INTO utilisateurs (nom, email) VALUES (?, ?)');
const result = insert.run('Alice', 'alice@mail.com');
console.log(result.lastInsertRowid);  // ID auto-généré → 1

// READ — lire toutes les lignes
const tous = db.prepare('SELECT * FROM utilisateurs').all();
console.log(tous);  // [{ id: 1, nom: 'Alice', email: '...' }]

// READ — lire une seule ligne par ID
const un = db.prepare('SELECT * FROM utilisateurs WHERE id = ?').get(1);

// UPDATE — modifier une ligne
db.prepare('UPDATE utilisateurs SET nom = ? WHERE id = ?').run('Alice Martin', 1);

// DELETE — supprimer une ligne
const del = db.prepare('DELETE FROM utilisateurs WHERE id = ?').run(1);
console.log(del.changes);  // nombre de lignes supprimées
Toujours utiliser prepare() avec des ? plutôt que de construire la requête avec une concaténation de string. Les ? sont des paramètres liés — ils protègent contre les injections SQL, la faille de sécurité la plus courante des applications web.
Injection SQL — si tu construis ta requête comme `SELECT * FROM users WHERE id = ${req.params.id}`, un attaquant peut passer 1 OR 1=1 et récupérer TOUTE la table. Avec prepare('... WHERE id = ?').get(req.params.id), le driver traite la valeur comme une donnée, jamais comme du SQL.
📐

Qu'est-ce qu'une API REST ?

Une API REST (Representational State Transfer) est une convention de conception d'API web. Elle repose sur deux idées : les ressources (ce qu'on manipule — utilisateurs, produits, tâches…) et les verbes HTTP (l'action qu'on effectue dessus).

Verbe HTTPAction CRUDExempleDescription
GETReadGET /tachesLire toutes les tâches
GETReadGET /taches/5Lire la tâche n°5
POSTCreatePOST /tachesCréer une nouvelle tâche
PUTUpdatePUT /taches/5Modifier la tâche n°5
DELETEDeleteDELETE /taches/5Supprimer la tâche n°5
Les codes de statut HTTP signalent le résultat d'une requête. Les plus courants dans une API : 200 OK, 201 Created (nouveau resource), 400 Bad Request (données invalides), 404 Not Found, 500 Internal Server Error.
🗂️

Structure du projet

On va construire une API de gestion de tâches. Voici la structure du projet :

api-taches/
├── package.json
├── node_modules/
├── db.js         # initialisation SQLite
└── app.js        # serveur Express + toutes les routes

Installe les deux dépendances nécessaires :

npm init -y
npm install express better-sqlite3
🗄️

Initialisation de la base de données

Le fichier db.js crée la base et la table au démarrage, puis exporte la connexion pour qu'on puisse l'utiliser depuis app.js.

// db.js
const Database = require('better-sqlite3');

const db = new Database('taches.db');
db.pragma('foreign_keys = ON');

db.exec(`
  CREATE TABLE IF NOT EXISTS taches (
    id      INTEGER PRIMARY KEY AUTOINCREMENT,
    titre   TEXT    NOT NULL,
    faite   INTEGER NOT NULL DEFAULT 0,
    cree_le TEXT    DEFAULT CURRENT_TIMESTAMP
  )
`);

module.exports = db;
📥

GET — lire les données

Deux routes GET : une pour lister toutes les tâches, une autre pour en récupérer une par son ID.

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

// GET /taches — liste toutes les tâches
app.get('/taches', (req, res) => {
  const taches = db.prepare('SELECT * FROM taches ORDER BY id DESC').all();
  res.json(taches);
});

// GET /taches/:id — une tâche par ID
app.get('/taches/:id', (req, res) => {
  const tache = db.prepare('SELECT * FROM taches WHERE id = ?').get(req.params.id);
  if (!tache) return res.status(404).json({ erreur: 'Tâche introuvable' });
  res.json(tache);
});
Tester avec curl http://localhost:3000/taches ou directement dans le navigateur (les GET fonctionnent dans la barre d'adresse). Pour POST/PUT/DELETE, utilise un outil comme Postman, Insomnia, ou l'extension VS Code Thunder Client.
📤

POST — créer une entrée

Le client envoie les données dans le body de la requête en JSON. On valide qu'elles sont présentes, puis on insère en base.

// POST /taches — créer une tâche
app.post('/taches', (req, res) => {
  const { titre } = req.body;

  if (!titre || titre.trim() === '') {
    return res.status(400).json({ erreur: 'Le champ "titre" est requis' });
  }

  const result = db.prepare('INSERT INTO taches (titre) VALUES (?)').run(titre.trim());

  res.status(201).json({
    id:    result.lastInsertRowid,
    titre: titre.trim(),
    faite: 0,
  });
});
Requête POST avec curl :
curl -X POST http://localhost:3000/taches -H "Content-Type: application/json" -d '{"titre":"Apprendre Node.js"}'

Réponse : {"id":1,"titre":"Apprendre Node.js","faite":0}
✏️

PUT — modifier une entrée

On commence par vérifier que la ressource existe, puis on met à jour uniquement les champs fournis (les autres gardent leur valeur courante).

// PUT /taches/:id — modifier une tâche
app.put('/taches/:id', (req, res) => {
  const tache = db.prepare('SELECT * FROM taches WHERE id = ?').get(req.params.id);
  if (!tache) return res.status(404).json({ erreur: 'Tâche introuvable' });

  const titre = req.body.titre ?? tache.titre;  // garde l'ancien si absent
  const faite = req.body.faite ?? tache.faite;

  db.prepare('UPDATE taches SET titre = ?, faite = ? WHERE id = ?')
    .run(titre, faite, req.params.id);

  res.json({ id: parseInt(req.params.id), titre, faite });
});
Marquer la tâche 1 comme faite :
curl -X PUT http://localhost:3000/taches/1 -H "Content-Type: application/json" -d '{"faite":1}'
🗑️

DELETE — supprimer une entrée

On supprime la ligne et on vérifie que quelque chose a bien été supprimé (via result.changes) pour renvoyer un 404 si l'ID n'existait pas.

// DELETE /taches/:id — supprimer une tâche
app.delete('/taches/:id', (req, res) => {
  const result = db.prepare('DELETE FROM taches WHERE id = ?').run(req.params.id);

  if (result.changes === 0) {
    return res.status(404).json({ erreur: 'Tâche introuvable' });
  }

  res.json({ message: 'Tâche supprimée avec succès' });
});
🏁

Le code complet — app.js

Voici le fichier app.js complet avec toutes les routes assemblées. Tu peux copier-coller ce fichier avec le db.js vu au chapitre précédent et démarrer immédiatement.

// app.js — API REST complète de gestion de tâches
const express = require('express');
const db      = require('./db');

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

// ── GET /taches ─────────────────────────────────────────────────────
app.get('/taches', (req, res) => {
  const taches = db.prepare('SELECT * FROM taches ORDER BY id DESC').all();
  res.json(taches);
});

// ── GET /taches/:id ─────────────────────────────────────────────────
app.get('/taches/:id', (req, res) => {
  const tache = db.prepare('SELECT * FROM taches WHERE id = ?').get(req.params.id);
  if (!tache) return res.status(404).json({ erreur: 'Tâche introuvable' });
  res.json(tache);
});

// ── POST /taches ─────────────────────────────────────────────────────
app.post('/taches', (req, res) => {
  const { titre } = req.body;
  if (!titre?.trim()) return res.status(400).json({ erreur: 'Le champ "titre" est requis' });
  const r = db.prepare('INSERT INTO taches (titre) VALUES (?)').run(titre.trim());
  res.status(201).json({ id: r.lastInsertRowid, titre: titre.trim(), faite: 0 });
});

// ── PUT /taches/:id ──────────────────────────────────────────────────
app.put('/taches/:id', (req, res) => {
  const tache = db.prepare('SELECT * FROM taches WHERE id = ?').get(req.params.id);
  if (!tache) return res.status(404).json({ erreur: 'Tâche introuvable' });
  const titre = req.body.titre ?? tache.titre;
  const faite = req.body.faite ?? tache.faite;
  db.prepare('UPDATE taches SET titre = ?, faite = ? WHERE id = ?').run(titre, faite, req.params.id);
  res.json({ id: parseInt(req.params.id), titre, faite });
});

// ── DELETE /taches/:id ───────────────────────────────────────────────
app.delete('/taches/:id', (req, res) => {
  const r = db.prepare('DELETE FROM taches WHERE id = ?').run(req.params.id);
  if (r.changes === 0) return res.status(404).json({ erreur: 'Tâche introuvable' });
  res.json({ message: 'Tâche supprimée avec succès' });
});

// ── Démarrage ────────────────────────────────────────────────────────
app.listen(3000, () => console.log('API démarrée → http://localhost:3000'));
Pour démarrer : node app.js. Pour recharger automatiquement à chaque modification du code : installe npm install -D nodemon puis lance npx nodemon app.js. Tu n'auras plus à redémarrer le serveur manuellement à chaque changement.
🧪

Tester l'API — flux complet

Voici une séquence de tests pour vérifier que tout fonctionne avec curl :

# 1. Créer deux tâches
curl -X POST http://localhost:3000/taches \
  -H "Content-Type: application/json" \
  -d '{"titre":"Apprendre Node.js"}'
# → {"id":1,"titre":"Apprendre Node.js","faite":0}

curl -X POST http://localhost:3000/taches \
  -H "Content-Type: application/json" \
  -d '{"titre":"Faire une API REST"}'
# → {"id":2,"titre":"Faire une API REST","faite":0}

# 2. Lister toutes les tâches
curl http://localhost:3000/taches
# → [{"id":2,...},{"id":1,...}]

# 3. Marquer la tâche 1 comme faite
curl -X PUT http://localhost:3000/taches/1 \
  -H "Content-Type: application/json" \
  -d '{"faite":1}'
# → {"id":1,"titre":"Apprendre Node.js","faite":1}

# 4. Supprimer la tâche 2
curl -X DELETE http://localhost:3000/taches/2
# → {"message":"Tâche supprimée avec succès"}

# 5. Tenter de supprimer une tâche inexistante
curl -X DELETE http://localhost:3000/taches/999
# → 404 {"erreur":"Tâche introuvable"}
Les navigateurs n'envoient que des requêtes GET quand tu tapes une URL. Pour tester les autres verbes (POST, PUT, DELETE), tu as besoin d'un outil dédié. Thunder Client (extension VS Code), Postman ou Insomnia te permettent de construire n'importe quelle requête HTTP visuellement.
🔮

Et ensuite ?

Tu as maintenant une API REST fonctionnelle avec persistance. C'est la base de toute application web moderne. Voici les prochaines étapes naturelles :

SujetPourquoiPackage clé
Authentification JWTProtéger les routes, identifier les utilisateursjsonwebtoken, bcrypt
Variables d'environnementNe pas coder les secrets en durdotenv
Middleware de validationValider les données entrantes proprementzod, joi
ORMManipuler la DB avec des objets JS plutôt que du SQL brutprisma, drizzle
MySQL / PostgreSQLPasser à une vraie base de productionmysql2, pg
Le code que tu as écrit ici est exactement le même pattern que les APIs en production — juste sans authentification et avec une base plus légère. La logique GET/POST/PUT/DELETE, la gestion des 404, la validation du body : c'est universel.
🌳

L'arborescence Linux

Sous Windows, chaque disque a sa propre lettre : C:\, D:\… Sous Linux, tout est regroupé dans un seul arbre qui part d'un unique point de départ : /, qu'on appelle la racine. Tous les fichiers et dossiers — qu'ils soient sur un SSD, une clé USB ou un disque réseau — sont quelque part dans cet arbre.

/                        ← racine (le sommet de tout)
├── home/
│   └── user/            ← ton dossier personnel (abrégé ~)
│       ├── documents/
│       ├── projets/
│       └── photos/
├── etc/                 ← fichiers de configuration système
├── var/
│   └── log/             ← journaux du système
└── tmp/                 ← fichiers temporaires

Quand tu ouvres un terminal, tu atterris automatiquement dans ton dossier personnel : /home/user. Ce dossier est tellement utilisé qu'il a un raccourci : le symbole ~ (tilde).

~ = /home/ton_utilisateur — Ces deux formes désignent exactement le même endroit. Tu les verras en permanence dans les exemples et dans le prompt du terminal.
📌

Chemin absolu vs chemin relatif

Il existe deux façons de désigner un emplacement :

TypeCommence parExempleSignification
Absolu//home/user/documentsDepuis la racine, toujours valide peu importe où tu es
Relatifun nom ou . ou ..documentsDepuis ton emplacement actuel

Si tu es dans /home/user, ces deux commandes mènent exactement au même endroit :

cd documents                # relatif  — part de /home/user
cd /home/user/documents    # absolu   — part de la racine

Les deux points .. désignent toujours le dossier parent, et on peut les enchaîner :

cd ..           # remonte d'un niveau
cd ../..        # remonte de deux niveaux
cd ../photos   # remonte puis entre dans photos
📋

Cheatsheet — pwd · ls · cd

pwd — Affiche l'emplacement courant

CommandeRésultat
pwdAffiche le chemin absolu du dossier courant, ex : /home/user/documents

ls — Liste les fichiers

CommandeEffet
lsListe les fichiers et dossiers du répertoire courant
ls -lFormat long : permissions, taille, date de modification
ls -aAffiche aussi les fichiers cachés (nom commençant par .)
ls -laFormat long + fichiers cachés (le plus complet)
ls -lhTailles en Ko/Mo plutôt qu'en octets (plus lisible)
ls /etcListe un dossier spécifique sans s'y déplacer

cd — Change de répertoire

CommandeDestination
cd documentsEntre dans le sous-dossier documents (relatif)
cd projets/webEnchaîne deux niveaux en une commande
cd /etcVa directement à /etc (absolu)
cd ..Remonte au dossier parent
cd ../photosRemonte puis entre dans photos
cd ~Retourne au dossier home (raccourci universel)
cdIdentique à cd ~
cd -Revient au dossier précédent (va-et-vient pratique)
🎮

Lab — Explore et découvre

Tu as accès à un terminal avec ce système de fichiers :

/home/user/
├── documents/       ← notes.txt · todo.txt · rapport.md
├── photos/
│   ├── vacances/    ← plage.jpg · montagne.jpg · mer.jpg
│   └── famille.jpg
├── projets/
│   ├── web/         ← index.html · style.css · app.js
│   └── python/      ← app.py · requirements.txt
└── ???              ← certains éléments ne se voient pas avec ls...

Chaque défi décrit ce que tu dois accomplir — pas la commande à utiliser. C'est à toi de la trouver. Tape hint si tu es bloqué, help pour voir toutes les commandes disponibles.

🔒

Atelier réservé aux membres

Connectez-vous pour accéder aux ateliers interactifs.

🎮 Lab — Navigation (7 défis)
💡

Lire un fichier

cat affiche tout le contenu d'un fichier d'un coup. Pour les grands fichiers, less permet de les parcourir page par page (flèches + q pour quitter). head et tail affichent le début ou la fin.

cat fichier.txt              # affiche tout le fichier
cat -n fichier.txt           # avec numéros de lignes

head fichier.txt             # 10 premières lignes (défaut)
head -n 3 fichier.txt        # seulement les 3 premières

tail fichier.txt             # 10 dernières lignes
tail -n 5 fichier.txt        # 5 dernières
tail -f /var/log/syslog      # suit le fichier en temps réel (logs live)
🔍

Chercher avec grep

grep (global regular expression print) filtre les lignes d'un fichier qui contiennent un motif. C'est l'un des outils les plus utilisés sous Linux. Il supporte les expressions régulières pour des recherches avancées.

grep "erreur" fichier.log       # lignes contenant "erreur"
grep -i "erreur" fichier.log   # insensible à la casse
grep -n "erreur" fichier.log   # affiche le numéro de ligne
grep -r "TODO" ./projets/      # cherche récursivement dans un dossier

wc -l fichier.txt               # compte les lignes
wc -w fichier.txt               # compte les mots
Pipe | : Tu peux enchaîner les commandes avec |. Ex : cat fichier.log | grep "erreur" | wc -l compte les lignes d'erreur.
🗂️

Créer et organiser — touch, mkdir, rmdir

Avant de lire ou modifier un fichier, il faut savoir le créer. Ces trois commandes sont la base de l'organisation de ton système de fichiers depuis le terminal.

touch — créer un fichier vide

touch crée un fichier vide s'il n'existe pas, ou met à jour sa date de modification s'il existe déjà. Très utilisé pour initialiser des fichiers ou déclencher des comportements basés sur les dates.

touch fichier.txt              # crée fichier.txt vide (ou met à jour sa date)
touch a.txt b.txt c.txt        # crée plusieurs fichiers d'un coup
touch scripts/deploy.sh        # crée dans un sous-dossier (doit exister)

mkdir — créer un dossier

mkdir mon-projet              # crée le dossier "mon-projet"
mkdir -p projets/web/assets   # crée toute l'arborescence d'un coup (-p)
mkdir src tests docs          # crée plusieurs dossiers en une commande
-p est indispensable quand les dossiers parents n'existent pas encore. Sans lui, mkdir projets/web/assets échoue si projets/web n'existe pas.

rmdir et rm — supprimer

rmdir dossier-vide            # supprime un dossier UNIQUEMENT s'il est vide
rm fichier.txt               # supprime un fichier
rm -r mon-dossier            # supprime un dossier et tout son contenu (récursif)
rm -rf mon-dossier           # idem, sans demander confirmation (-f = force)
rm -rf est irréversible. Il n'y a pas de corbeille en ligne de commande — les fichiers supprimés sont perdus définitivement. Double-vérifie toujours le chemin avant d'appuyer sur Entrée.

Copier et déplacer

cp source.txt copie.txt       # copie un fichier
cp -r dossier/ sauvegarde/  # copie récursive d'un dossier
mv ancien.txt nouveau.txt    # renomme un fichier
mv fichier.txt dossier/      # déplace un fichier dans un dossier
mv dossier/ /var/www/html/   # déplace un dossier entier
✏️

Éditer un fichier — nano et vi

Lire un fichier c'est bien, le modifier c'est indispensable. Sur un serveur Linux sans interface graphique, tout passe par un éditeur en ligne de commande. Les deux incontournables sont nano (accessible immédiatement, idéal pour débuter) et vi / vim (très puissant, présent sur toutes les machines Linux sans exception, mais avec une logique à apprivoiser).

ÉditeurLancementDifficultéDisponibilité
nanonano fichier.txtFacile — raccourcis visibles à l'écranUbuntu / Debian (par défaut)
vivi fichier.txtAvancé — logique modale à comprendreToutes distributions Linux
vimvim fichier.txtAvancé — version améliorée de viÀ installer : sudo apt install vim

nano — simple et immédiat

Quand tu ouvres nano, tu vois le contenu du fichier en haut et deux lignes de raccourcis en bas de l'écran. Le symbole ^ signifie Ctrl. Tu peux commencer à taper immédiatement, sans rien apprendre de particulier. C'est l'éditeur parfait pour modifier rapidement un fichier de configuration, un script ou un fichier texte.

nano fichier.txt         # ouvre le fichier (le crée s'il n'existe pas)
sudo nano /etc/hosts     # édite un fichier système (nécessite sudo)

Une fois dans nano :

# Navigation
Flèches             # déplacer le curseur
Ctrl+Home           # aller au début du fichier
Ctrl+End            # aller à la fin du fichier

# Édition
Ctrl+K              # couper la ligne courante
Ctrl+U              # coller la ligne coupée

# Recherche
Ctrl+W              # rechercher un mot (Entrée pour valider, Ctrl+W encore pour suivant)
Ctrl+\              # rechercher et remplacer

# Sauvegarder / Quitter
Ctrl+S              # sauvegarder sans quitter
Ctrl+O              # "Write Out" — sauvegarder sous un nom (confirme avec Entrée)
Ctrl+X              # quitter (propose de sauvegarder si des modifs existent)
Ctrl+G              # aide complète

vi / vim — l'éditeur qui est partout

vi est installé sur absolument toutes les distributions Linux, y compris les serveurs minimalistes où nano n'est pas disponible. C'est pourquoi savoir l'utiliser — même basiquement — est une compétence essentielle. Sa particularité : il fonctionne par modes. Quand tu ouvres vi, tu es en mode Normal (pour naviguer et lancer des commandes). Pour écrire du texte, tu dois d'abord passer en mode Insertion. C'est déconcertant au début, mais très efficace une fois maîtrisé.

ModeÀ quoi ça sertComment y accéder
NormalNaviguer, copier, supprimer, lancer des commandesTouche de départ / Esc
InsertionÉcrire du texte comme dans un éditeur classiquei, a, o depuis le mode Normal
CommandeSauvegarder, quitter, chercher, remplacer: depuis le mode Normal
vi fichier.txt        # ouvre le fichier — tu arrives en mode Normal

# Passer en mode Insertion (pour écrire) :
i                    # insère avant le curseur
a                    # insère après le curseur
o                    # ouvre une nouvelle ligne en dessous et passe en Insertion
Esc                  # revient toujours en mode Normal

# Navigation en mode Normal :
h  j  k  l          # gauche / bas / haut / droite (alternatives aux flèches)
gg                   # aller à la première ligne
G                    # aller à la dernière ligne
:42                  # aller à la ligne 42

# Édition en mode Normal :
x                    # supprimer le caractère sous le curseur
dd                   # supprimer (couper) la ligne entière
yy                   # copier la ligne entière
p                    # coller après le curseur
u                    # annuler la dernière action (undo)
Ctrl+R               # rétablir (redo)

# Recherche et remplacement :
/motif               # rechercher "motif" (n pour suivant, N pour précédent)
:%s/ancien/nouveau/g # remplacer toutes les occurrences dans le fichier

# Sauvegarder / Quitter (mode Commande — taper : d'abord) :
:w                   # sauvegarder
:q                   # quitter (échoue si des modifs non sauvegardées)
:wq                  # sauvegarder ET quitter
:q!                  # quitter sans sauvegarder (abandon des modifs)
Bloqué dans vi ? C'est le piège classique du débutant. Appuie sur Esc plusieurs fois pour t'assurer d'être en mode Normal, puis tape :q! et Entrée pour quitter sans sauvegarder. La prochaine fois : i pour écrire, Esc pour arrêter d'écrire, :wq pour sauvegarder et quitter.
↔️

Redirections et flux — stdin, stdout, stderr

Chaque processus Linux communique avec le monde extérieur via trois flux de données standardisés. Par défaut, ils sont tous branchés sur ton terminal — mais tu peux les rediriger vers des fichiers, d'autres commandes, ou même les supprimer. C'est une des fonctionnalités les plus puissantes du shell.

FluxNuméroPar défautRôle
stdin0ClavierEntrée — ce que la commande lit
stdout1ÉcranSortie normale — le résultat de la commande
stderr2ÉcranSortie d'erreur — les messages d'erreur uniquement

Rediriger stdout vers un fichier

Au lieu d'afficher le résultat dans le terminal, tu peux l'écrire directement dans un fichier. Deux opérateurs : > qui écrase le fichier, et >> qui ajoute à la fin (append) sans rien effacer.

# Créer / écraser un fichier
echo "Bonjour" > message.txt          # crée le fichier avec "Bonjour" (ou l'écrase)
ls -l > liste.txt                    # sauvegarde le résultat de ls dans un fichier
cat a.txt b.txt > fusion.txt          # concatène deux fichiers en un seul

# Ajouter sans écraser (append)
echo "Ligne 2" >> message.txt         # ajoute à la fin du fichier existant
echo "$(date) — démarrage" >> app.log  # journalise une entrée avec la date
Attention avec > : si le fichier existe déjà, son contenu est immédiatement et silencieusement effacé. Il n'y a pas de confirmation. En cas de doute, utilise toujours >>.

Rediriger les erreurs (stderr)

ls /dossier-inexistant 2> erreurs.txt      # redirige stderr vers un fichier
ls /dossier > out.txt 2> err.txt          # sépare stdout et stderr dans deux fichiers
ls /dossier > tout.txt 2>&1              # fusionne stderr dans stdout → un seul fichier
ls /dossier 2> /dev/null               # supprime les erreurs (/dev/null = "poubelle" Linux)
commande > /dev/null 2>&1             # silence total : stdout et stderr supprimés

Lire depuis un fichier (stdin)

wc -l < notes.txt       # compte les lignes de notes.txt via stdin
sort < liste.txt         # trie le contenu du fichier

Le pipe | — chaîner les commandes

Le pipe | connecte le stdout d'une commande directement au stdin de la suivante — sans passer par un fichier intermédiaire. C'est la philosophie Unix : des outils simples qui font une seule chose bien, assemblés pour résoudre des problèmes complexes.

cat notes.txt | grep "Docker"             # lignes contenant "Docker"
cat notes.txt | grep "Docker" | wc -l      # combien de lignes contiennent "Docker" ?
ls -l | grep "^d"                         # liste uniquement les dossiers
ps aux | grep "nginx"                    # nginx est-il en cours d'exécution ?
cat /var/log/syslog | grep "error" | tail -n 5  # 5 dernières erreurs

tee — écrire ET afficher en même temps

tee lit stdin et l'envoie à la fois vers stdout et vers un fichier. Pratique quand tu veux voir le résultat en direct tout en le sauvegardant.

ls -l | tee liste.txt      # affiche ET sauvegarde dans liste.txt
ls | tee -a journal.txt   # -a pour append (ne pas écraser)
🖥️

Lab — Pratique interactive

Un fichier de mission t'attend quelque part dans le système. Suis les pistes de fichier en fichier pour terminer la mission — chaque fichier lu révèle l'étape suivante.

🔒

Atelier réservé aux membres

Connectez-vous pour accéder aux ateliers interactifs.

🖥️ Lab — Fichiers (5 pistes)
bash — user@ubuntu: ~
💡

Comment fonctionnent les paquets ?

Ubuntu maintient des dépôts (repositories) — des serveurs qui contiennent des milliers de logiciels pré-compilés et testés. apt update télécharge la liste des paquets disponibles. apt install télécharge et installe un paquet avec toutes ses dépendances automatiquement.

Toujours mettre à jour la liste avant d'installer : sudo apt update && sudo apt install nom-paquet
⌨️

Commandes essentielles

Deux règles d'or : toujours commencer par sudo apt update avant d'installer quoi que ce soit, et préférer apt purge à apt remove pour ne pas laisser de fichiers de configuration orphelins.

sudo apt update              # rafraîchit la liste des paquets disponibles
sudo apt upgrade             # met à jour tous les paquets installés
sudo apt full-upgrade        # upgrade + supprime les paquets obsolètes

sudo apt install curl       # installe le paquet "curl"
sudo apt install curl wget git # plusieurs paquets à la fois
sudo apt remove curl        # désinstalle (conserve la config)
sudo apt purge curl         # désinstalle + supprime la config
sudo apt autoremove          # supprime les dépendances inutiles

apt search htop             # cherche un paquet dans les dépôts
apt show nginx              # détails sur un paquet
apt list --installed         # liste les paquets installés
sudo : Les commandes apt qui modifient le système nécessitent les droits administrateur via sudo. La lecture (apt search, apt show) ne le nécessite pas.
📦

Snap — le magasin universel de Canonical

En parallèle d'apt, Ubuntu propose Snap : un format de paquet développé par Canonical où chaque application embarque toutes ses dépendances dans un conteneur isolé. Avantage : la même version fonctionne sur n'importe quelle distribution Linux. Inconvénient : les paquets sont plus lourds et le démarrage parfois plus lent qu'avec apt.

aptsnap
DépendancesPartagées avec le systèmeEmbarquées dans le paquet
Mises à jourManuelles (apt upgrade)Automatiques en arrière-plan
IsolationAccès complet au systèmeSandbox — accès restreint
TailleLégèrePlus lourde (dépendances incluses)
Utilisation idéalePaquets système, serveursApplications desktop, outils dev
snap install vlc           # installe VLC depuis le Snap Store
snap install code --classic # --classic = accès complet au système (nécessaire pour VS Code)
snap list                    # liste les snaps installés
snap remove vlc            # désinstalle un snap
snap find editor            # cherche dans le Snap Store
snap info vlc              # détails et versions disponibles d'un snap
snap refresh                 # met à jour tous les snaps manuellement
Quand choisir snap ? Pour des applications desktop (navigateurs, éditeurs, outils de communication) ou quand tu veux la dernière version d'un logiciel sans attendre les dépôts Ubuntu. Pour les services système (nginx, postgresql…), préfère toujours apt.
🔄

Mises à jour automatiques — unattended-upgrades

Sur un serveur, il est crucial d'appliquer les mises à jour de sécurité régulièrement — mais personne ne surveille un serveur en permanence. unattended-upgrades est un outil qui installe automatiquement les correctifs de sécurité chaque nuit, sans intervention humaine. C'est une bonne pratique essentielle dès qu'une machine est exposée sur internet.

Installation et activation

sudo apt install unattended-upgrades              # installe l'outil
sudo dpkg-reconfigure -plow unattended-upgrades   # active via un assistant interactif

Vérifier et tester

sudo unattended-upgrades --dry-run   # simule sans installer — liste ce qui serait mis à jour
sudo unattended-upgrades -v          # exécute réellement les mises à jour
cat /var/log/unattended-upgrades/unattended-upgrades.log  # journal des mises à jour

Configuration

Le comportement se configure dans /etc/apt/apt.conf.d/50unattended-upgrades. Par défaut, seules les mises à jour de sécurité sont installées automatiquement — les mises à jour fonctionnelles restent manuelles.

# Extrait de /etc/apt/apt.conf.d/50unattended-upgrades
Unattended-Upgrade::Allowed-Origins {
    "${distro_id}:${distro_codename}-security";  // mises à jour sécurité (activé)
    // "${distro_id}:${distro_codename}-updates";  // autres updates (désactivé)
};
Unattended-Upgrade::Mail "admin@monserveur.fr";  // notif email après chaque run
Unattended-Upgrade::Automatic-Reboot "false";    // ne pas redémarrer automatiquement
Bonne pratique : active unattended-upgrades sur tous tes serveurs de production dès la mise en place. Les failles de sécurité non patchées sont la principale cause de compromission des serveurs Linux accessibles depuis internet.
🖥️

Lab — Pratique interactive

🔒

Atelier réservé aux membres

Connectez-vous pour accéder aux ateliers interactifs.

🖥️ Lab — Paquets apt (6 défis)
bash — user@ubuntu: ~
💡

Surveiller les processus

ps prend un instantané des processus. top et htop affichent une vue en temps réel, triée par consommation CPU ou mémoire. Pour arrêter un processus, on envoie un signal avec kill.

ps                     # tes processus dans le terminal actuel
ps aux                 # TOUS les processus du système
ps aux | grep nginx    # filtrer par nom de processus

top                    # vue temps réel (q pour quitter)
htop                   # version améliorée et colorée (à installer)

kill 1234              # envoie SIGTERM au processus PID 1234 (arrêt propre)
kill -9 1234          # SIGKILL — force l'arrêt immédiat
killall firefox       # tue tous les processus nommés "firefox"
💾

Disque, mémoire, système

df affiche l'espace disponible sur chaque partition montée. du mesure la taille réelle occupée par un dossier ou un ensemble de fichiers. free donne l'état de la mémoire RAM. Ces trois commandes sont les premières à lancer quand un serveur ralentit ou refuse de démarrer.

df -h                  # espace disque de toutes les partitions (-h = lisible)
du -sh dossier/       # taille d'un dossier spécifique
du -sh *              # taille de chaque élément du dossier courant

free -h               # mémoire RAM et swap disponibles

uname -a              # version du noyau et infos système
uptime                 # temps de fonctionnement et charge système
lscpu                  # informations sur le processeur
🖥️

Lab — Pratique interactive

🔒

Atelier réservé aux membres

Connectez-vous pour accéder aux ateliers interactifs.

🖥️ Lab — Processus (4 défis)
bash — user@ubuntu: ~
💡

Lire les permissions

Quand tu fais ls -l, chaque ligne commence par 10 caractères qui décrivent les permissions :

-rwxr-xr--  1 user group  1234 Sep 4 script.sh
 ↑↑↑↑↑↑↑↑↑↑
 │└─┬─┘└─┬─┘└─┬─┘
 │  │    │    └── Autres  (r--)
 │  │    └─────── Groupe  (r-x)
 │  └──────────── Propriétaire (rwx)
 └─────────────── d=dossier, -=fichier, l=lien
LettreValeur numériqueSignification
r4Read — lire le fichier
w2Write — modifier le fichier
x1Execute — exécuter le fichier
-0Permission absente
Calcul : 755 = propriétaire (7=rwx), groupe (5=r-x), autres (5=r-x). 644 = propriétaire (6=rw-), groupe (4=r--), autres (4=r--).
⌨️

Commandes de permissions

chmod change les permissions d'un fichier, en notation numérique (755) ou symbolique (+x). chown change le propriétaire. sudo permet d'exécuter une commande en tant que super-utilisateur (root) — à utiliser avec précaution, car root peut modifier n'importe quel fichier du système.

chmod 755 script.sh        # rwxr-xr-x (notation numérique)
chmod +x script.sh         # rend exécutable (notation symbolique)
chmod -w fichier.txt       # enlève le droit d'écriture
chmod 644 config.conf      # lecture seule pour groupe et autres
chmod -R 755 dossier/     # applique récursivement

chown alice fichier.txt    # change le propriétaire
chown alice:devs fichier  # change propriétaire ET groupe

whoami                      # affiche ton utilisateur courant
sudo commande              # exécute en tant que root
sudo -i                    # ouvre un shell root
adduser alice              # crée un utilisateur
usermod -aG sudo alice    # ajoute alice au groupe sudo
🖥️

Lab — Pratique interactive

🔒

Atelier réservé aux membres

Connectez-vous pour accéder aux ateliers interactifs.

🖥️ Lab — Permissions (5 défis)
bash — user@ubuntu: ~
💡

Diagnostiquer le réseau

ip addr remplace l'ancienne commande ifconfig pour afficher les interfaces réseau et leurs adresses IP. ping teste la connectivité en envoyant des paquets ICMP vers une cible.

ip addr                       # affiche toutes les interfaces réseau et IPs
ip addr show eth0             # interface spécifique
ip route                      # table de routage (passerelle par défaut)

ping google.com              # teste la connectivité (Ctrl+C pour arrêter)
ping -c 4 8.8.8.8           # envoie exactement 4 paquets

ss -tlnp                     # ports en écoute (remplace netstat)
🌍

Requêtes HTTP & transfert

curl est l'outil universel pour faire des requêtes HTTP en ligne de commande : tester une API, télécharger un fichier, vérifier des en-têtes. wget est plus simple pour les téléchargements. scp permet de copier des fichiers de façon sécurisée vers un serveur distant via SSH.

curl https://exemple.com     # affiche le contenu HTTP
curl -o fichier.zip https://url # télécharge dans un fichier
curl -I https://exemple.com  # affiche seulement les en-têtes HTTP
curl -X POST -d '{"key":"val"}' https://api

wget https://url/fichier.tar.gz # télécharge un fichier

ssh user@192.168.1.10        # connexion SSH
ssh -p 2222 user@serveur    # port personnalisé
scp fichier.txt user@host:/tmp # copie sécurisée vers un serveur
scp -r dossier/ user@host:~/  # copie récursive
🖥️

Lab — Pratique interactive

🔒

Atelier réservé aux membres

Connectez-vous pour accéder aux ateliers interactifs.

🖥️ Lab — Réseau (5 défis)
bash — user@ubuntu: ~
🗂️

Nommage des disques sous Linux

Sous Linux, les périphériques de stockage ne s'appellent pas C: ou D: comme sous Windows. Ils sont représentés par des fichiers dans /dev, avec un nommage systématique. Comprendre ce nommage est indispensable pour monter des partitions, lire les logs disque ou configurer /etc/fstab.

NomTypeExemple
/dev/sdaPremier disque SATA/SCSI/USBsda1 = 1ère partition
/dev/sdbDeuxième disquesdb1, sdb2
/dev/nvme0n1Disque NVMenvme0n1p1 = 1ère partition
/dev/vdaDisque virtuel (VM)Courant sur les VPS cloud
📊

Espace disque et partitions

# Espace utilisé par partition
df -h                        # toutes les partitions montées (-h = human-readable)
df -h /                      # uniquement la partition racine

# Espace occupé par des dossiers
du -sh *                     # taille de chaque élément du dossier courant
du -sh /var/log             # taille totale d'un dossier
du -sh * | sort -h         # trié du plus petit au plus grand

# Lister les disques et partitions
lsblk                         # arborescence disques/partitions
lsblk -f                     # + système de fichiers et UUID
sudo fdisk -l               # détails complets de tous les disques
🔌

Monter et démonter une partition

Sous Linux, accéder à une partition signifie la monter sur un point de montage — un dossier vide qui sert de point d'entrée. Le contenu du disque apparaît alors dans ce dossier.

# Monter une partition
sudo mount /dev/sdb1 /mnt/data         # monte sdb1 dans /mnt/data
sudo mount -t ext4 /dev/sdb1 /mnt/data # en précisant le système de fichiers
mount -a                               # monte tout ce qui est dans /etc/fstab

# Démonter une partition
sudo umount /mnt/data                 # démonter par point de montage
sudo umount /dev/sdb1                 # ou par périphérique

# Voir ce qui est actuellement monté
mount | grep "^/dev"                 # partitions réelles uniquement
findmnt                               # arborescence lisible des points de montage
Montage permanent : pour qu'une partition soit montée automatiquement au démarrage, ajoute-la dans /etc/fstab avec son UUID (récupéré via lsblk -f). Utilise l'UUID plutôt que /dev/sdb1 car les lettres peuvent changer selon l'ordre de détection.
🛠️

Créer et formater une partition

Sur un disque vierge ou un nouveau VPS, il faut créer une table de partitions, créer les partitions, puis les formater avec un système de fichiers avant de pouvoir les utiliser.

# Partitionner avec fdisk (interactif)
sudo fdisk /dev/sdb     # lance l'outil interactif (n=nouvelle, w=écrire, q=quitter)

# Formater une partition
sudo mkfs.ext4 /dev/sdb1    # format ext4 (standard Linux)
sudo mkfs.xfs /dev/sdb1     # format XFS (performant pour gros fichiers)
sudo mkfs.vfat /dev/sdb1    # format FAT32 (compatible Windows/Mac)
📈

Agrandir un disque sans redémarrer (VMware / Nutanix)

Sur un hyperviseur (VMware, Nutanix, Proxmox…), on peut augmenter la taille d'un disque à chaud depuis la console d'administration. Mais Ubuntu ne détecte pas ce changement automatiquement — il faut enchaîner quatre opérations côté OS pour que l'espace soit réellement utilisable, sans redémarrer.

ÉtapeCe qu'on faitCommande
1Vérifier que le noyau voit la nouvelle taillesudo fdisk -l /dev/sda
2Agrandir la partitionsudo growpart /dev/sda 2
3Informer le noyau sans rebootsudo partprobe /dev/sda
4Agrandir le système de fichierssudo resize2fs /dev/sda2
5Vérifier le résultatdf -h

Étape 1 — Vérifier que le noyau voit la nouvelle taille

# Forcer le rescan du disque
echo 1 | sudo tee /sys/class/block/sda/device/rescan

# Vérifier que la nouvelle taille est visible
sudo fdisk -l /dev/sda      # la ligne "Disk /dev/sda" affiche la nouvelle taille

Étape 2 — Agrandir la partition avec growpart

growpart étend une partition existante jusqu'à la fin du disque, sans la supprimer ni la recréer. Plus sûr que de manipuler fdisk à la main.

sudo apt install cloud-guest-utils    # si growpart n'est pas installé
sudo growpart /dev/sda 2             # étend la partition 2 jusqu'à la fin du disque

Étape 3 — partprobe : éviter le redémarrage

Après une modification de la table de partitions, le noyau doit en être informé. Sans partprobe, il faudrait redémarrer le serveur — inacceptable en production.

sudo partprobe /dev/sda       # relit la table de partitions en live, sans reboot
partprobe vs reboot : sur un serveur de production, redémarrer peut signifier plusieurs minutes d'indisponibilité. partprobe informe le noyau des changements en quelques millisecondes, à chaud.

Étape 4 — Agrandir le système de fichiers

La partition est plus grande, mais le système de fichiers n'occupe pas encore le nouvel espace. Cette étape dépend du type de FS :

# ext4 — le plus courant sur Ubuntu
sudo resize2fs /dev/sda2

# XFS — courant sur RHEL / Rocky Linux
sudo xfs_growfs /

# LVM — si le disque utilise des volumes logiques
sudo pvresize /dev/sda2
sudo lvextend -l +100%FREE /dev/mapper/ubuntu--vg-ubuntu--lv
sudo resize2fs /dev/mapper/ubuntu--vg-ubuntu--lv

Étape 5 — Vérifier

df -h    # le nouvel espace apparaît sur la partition /
🖥️

Lab — Pratique interactive

🔒

Atelier réservé aux membres

Connectez-vous pour accéder aux ateliers interactifs.

🖥️ Lab — Disques (5 défis)
bash — user@ubuntu: ~
⏱️

Cron — planifier des tâches

Cron est un démon qui s'exécute en arrière-plan et lance des commandes à des horaires précis. Chaque utilisateur a sa propre table de tâches (crontab). Une entrée crontab suit ce format :

# ┌─ minute       (0-59)
# │ ┌─ heure        (0-23)
# │ │ ┌─ jour/mois   (1-31)
# │ │ │ ┌─ mois        (1-12)
# │ │ │ │ ┌─ jour/semaine (0=dim, 1=lun … 6=sam)
# │ │ │ │ │
  * * * * *  commande
ExpressionSignification
0 3 * * *Tous les jours à 3h00
*/15 * * * *Toutes les 15 minutes
0 8 * * 1Chaque lundi à 8h00
30 23 1 * *Le 1er de chaque mois à 23h30
@rebootAu démarrage du système
@dailyUne fois par jour (= 0 0 * * *)
crontab -e          # édite ta crontab (ouvre nano ou vi)
crontab -l          # liste tes tâches planifiées
crontab -r          # supprime toute ta crontab (attention !)
crontab -u alice -l # voir la crontab d'un autre utilisateur (root)

Exemples concrets de tâches cron :

# Backup quotidien à 2h du matin
0 2 * * * /home/user/scripts/backup.sh >> /var/log/backup.log 2>&1

# Nettoyage des fichiers temporaires chaque dimanche à minuit
0 0 * * 0 find /tmp -mtime +7 -delete

# Envoyer un rapport toutes les heures
0 * * * * /usr/local/bin/rapport.sh | mail -s "Rapport" admin@exemple.fr

# Lancer un script au démarrage
@reboot /home/user/scripts/start-services.sh
Rediriger les sorties : cron n'affiche pas dans un terminal. Redirige toujours stdout et stderr vers un fichier log (>> /var/log/ma-tache.log 2>&1) pour pouvoir déboguer en cas de problème.
📝

Scripts shell — automatiser avec bash

Un script shell est un fichier texte contenant une suite de commandes bash. Il suffit de lui donner les droits d'exécution pour pouvoir le lancer comme un programme.

Structure minimale d'un script

#!/bin/bash
# La première ligne (shebang) indique quel interpréteur utiliser

echo "Début du script"

# Variables
NOM="Alice"
DATE=$(date +%Y-%m-%d)         # capture la sortie d'une commande
echo "Bonjour $NOM, nous sommes le $DATE"

# Condition
if [ -f "/etc/hosts" ]; then
    echo "Le fichier hosts existe"
else
    echo "Introuvable"
fi

# Boucle
for fichier in /var/log/*.log; do
    echo "Log trouvé : $fichier"
done

echo "Script terminé"

Créer et exécuter un script

nano backup.sh                   # crée et édite le script
chmod +x backup.sh               # rend le script exécutable
./backup.sh                       # exécute depuis le dossier courant
bash backup.sh                    # exécute sans avoir besoin de +x

# Placer le script dans le PATH pour l'appeler de partout
sudo mv backup.sh /usr/local/bin/backup
backup                            # maintenant accessible depuis n'importe où

Exemple complet — script de backup

#!/bin/bash
REPERTOIRE="/home/user/projets"
DESTINATION="/mnt/backup"
DATE=$(date +%Y%m%d_%H%M%S)
ARCHIVE="backup_$DATE.tar.gz"

echo "[$DATE] Démarrage du backup..."
tar -czf "$DESTINATION/$ARCHIVE" "$REPERTOIRE"

if [ $? -eq 0 ]; then
    echo "[$DATE] Backup réussi : $ARCHIVE"
else
    echo "[$DATE] ERREUR lors du backup !" >&2
    exit 1
fi
$? contient le code de retour de la dernière commande : 0 = succès, autre = erreur. C'est la base de toute gestion d'erreur dans un script shell.
🖥️

Lab — Pratique interactive

🔒

Atelier réservé aux membres

Connectez-vous pour accéder aux ateliers interactifs.

🖥️ Lab — Crontab & Scripts (4 défis)
bash — user@ubuntu: ~
💡

Concepts clés

Avant de taper la première commande, il est essentiel de comprendre les quatre concepts fondamentaux de Docker. Beaucoup de gens confondent image et conteneur — la distinction est simple mais cruciale.

TermeDescription
ImageModèle en lecture seule (template). Ex: ubuntu, nginx, node
ConteneurInstance en cours d'exécution d'une image
DockerfileScript qui décrit comment construire une image
VolumeStockage persistant partagé entre hôte et conteneur
RegistryDépôt d'images (Docker Hub, registres privés)
▶️

Images et conteneurs

Les images se téléchargent depuis Docker Hub (le registre public) avec docker pull. Un conteneur est créé à partir d'une image avec docker run. Le flag -d lance en arrière-plan (detached), -p mappe un port de la machine hôte vers le conteneur.

# Images
docker pull nginx                    # télécharge une image
docker images                        # liste les images locales
docker rmi nginx                     # supprime une image

# Conteneurs
docker run -d -p 8080:80 nginx     # démarre en arrière-plan, port 8080→80
docker run --name mon-app -d nginx
docker run -it ubuntu bash         # interactif avec shell

docker ps                            # conteneurs actifs
docker ps -a                        # tous (actifs + stoppés)
docker stop mon-app
docker rm mon-app
docker logs -f mon-app             # logs en direct
docker exec -it mon-app bash       # ouvre un shell dans le conteneur
🏗️

Dockerfile

Un Dockerfile est un script qui décrit, étape par étape, comment construire une image Docker. On part toujours d'une image de base (ici node:20-alpine, une version légère de Node.js), on copie le code, on installe les dépendances, puis on définit la commande de démarrage. Chaque instruction crée une couche — Docker met en cache les couches inchangées pour accélérer les builds.

# Dockerfile — Application Node.js
FROM node:20-alpine

WORKDIR /app

COPY package*.json ./
RUN npm install

COPY . .

EXPOSE 3000
CMD ["node", "server.js"]
docker build -t mon-app:1.0 .
docker run -d -p 3000:3000 mon-app:1.0
🔗

Docker Compose

Une application réelle est rarement composée d'un seul conteneur : une API, une base de données, un reverse proxy… Docker Compose permet de décrire et démarrer plusieurs services dans un seul fichier docker-compose.yml. Une commande suffit pour tout lancer ou tout arrêter.

# docker-compose.yml
services:
  web:
    image: nginx
    ports: ["8080:80"]
    depends_on: [db]

  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: secret
    volumes: [db-data:/var/lib/postgresql/data]

volumes:
  db-data:
docker compose up -d       # démarre tous les services
docker compose down         # arrête et supprime
docker compose logs -f     # logs en direct
docker compose down -v     # supprime aussi les volumes
💡

C'est quoi HTML ?

HTML signifie HyperText Markup Language. Ce n'est pas un langage de programmation, c'est un langage de balisage. On décrit le contenu en l'entourant de balises, et le navigateur sait comment l'afficher.

TechnologieRôleAnalogie
HTMLStructure et contenuLa charpente d'une maison
CSSApparence et mise en pageLa peinture, le parquet
JavaScriptComportement et interactivitéL'électricité, la domotique

Un fichier HTML est un simple fichier texte avec l'extension .html. Aucun logiciel spécial pour le créer, Notepad suffit. N'importe quel navigateur peut l'ouvrir.

🌐

Comment ça marche : du fichier au navigateur

Quand tu ouvres une page web, voici ce qui se passe :

  1. Tu tapes une URL dans ton navigateur (ou tu double-cliques sur un fichier .html)
  2. Le navigateur envoie une requête HTTP à un serveur web
  3. Le serveur renvoie le fichier HTML
  4. Le navigateur lit le HTML et construit l'affichage
  5. Il télécharge aussi le CSS et JavaScript liés, et les applique
sequenceDiagram
    participant B as 🌐 Navigateur
    participant S as 🖥️ Serveur web (Apache/Nginx)

    B->>S: GET /index.html
    S-->>B: 200 OK + HTML

    B->>S: GET /style.css
    S-->>B: 200 OK + CSS

    B->>S: GET /app.js
    S-->>B: 200 OK + JavaScript

    Note over B: Affiche la page ✅
      

GET /index.html signifie "donne-moi ce fichier". Le serveur répond 200 OK quand tout va bien et renvoie le fichier demandé. Ce mécanisme de demande/réponse s'appelle le protocole HTTP : c'est la langue que parlent navigateur et serveur pour communiquer. Tu le verras souvent dans l'onglet Network des DevTools.

En local : pendant le développement, tu peux ouvrir directement le fichier index.html dans ton navigateur (sans serveur). En production, les fichiers sont hébergés sur un serveur web accessible via internet.
🏗️

La structure minimale d'une page

Pour commencer, crée un dossier sur ton ordinateur pour ton site web, appelle-le par exemple mon-site. À l'intérieur, crée un fichier que tu nommes index.html. Ce nom est une convention universelle : c'est la page que le serveur sert en premier quand on visite un site. Ouvre ce fichier dans VSCode et tu peux commencer à coder.

Si tu travailles à plusieurs sur ce projet, tu peux versionner ce dossier avec Git et le pousser sur GitHub pour que toute l'équipe puisse contribuer et voir les modifications de chacun en temps réel.

Toute page HTML valide commence par une déclaration <!DOCTYPE html> qui indique au navigateur qu'il s'agit de HTML5. Ensuite, tout le contenu est dans la balise <html>.

<!DOCTYPE html>
<html lang="fr">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Ma première page</title>
</head>
<body>
  <h1>Bonjour le monde !</h1>
  <p>Mon premier paragraphe.</p>
</body>
</html>
BaliseRôle
<!DOCTYPE html>Déclare que c'est du HTML5
<html lang="fr">Racine du document, langue française
<head>Métadonnées (titre, encodage, CSS…), non affiché
<body>Tout ce qui est visible sur la page
🔤

Les balises : ouvrantes et fermantes

La plupart des balises HTML fonctionnent en paires : une balise ouvrante <p> et une balise fermante </p>. Le contenu se trouve entre les deux. Certaines balises sont auto-fermantes car elles ne contiennent pas de contenu.

<p>Ceci est un paragraphe.</p>

<img src="photo.jpg" alt="Description">  <!-- auto-fermante -->
<br>                                      <!-- saut de ligne, auto-fermante -->
<input type="text">                       <!-- champ de saisie, auto-fermante -->
Oublier de fermer une balise est une erreur très fréquente. Le navigateur essaie de corriger, mais le résultat peut être imprévisible. Prenez l'habitude de toujours fermer vos balises.
⚙️

Les attributs

Les attributs apportent des informations supplémentaires à une balise. Ils s'écrivent toujours dans la balise ouvrante, sous la forme nom="valeur". Certains attributs sont obligatoires (comme src sur <img>), d'autres sont optionnels.

<!-- src = chemin de l'image, alt = description -->
<img src="photo.jpg" alt="Portrait de l'équipe">

<!-- href = destination du lien, target = où ouvrir -->
<a href="https://exemple.com" target="_blank">Visiter le site</a>

<!-- class = appliquer un style CSS -->
<p class="important">Ce texte aura un style particulier.</p>

<!-- id = identifiant unique sur la page -->
<section id="contact">...</section>

<!-- lang = langue du document -->
<html lang="fr">
AttributUtilisation
classApplique un style CSS, réutilisable sur plusieurs éléments
idIdentifiant unique, pour cibler un seul élément
hrefURL de destination d'un lien <a>
srcSource d'une image ou d'un script
altTexte alternatif si l'image ne charge pas (accessibilité)
target="_blank"Ouvre le lien dans un nouvel onglet
styleStyle CSS en ligne (à éviter, préférer les classes)
💬

Les commentaires HTML

Les commentaires sont ignorés par le navigateur. Ils servent à documenter votre code ou à désactiver temporairement une partie de la page. Ils commencent par <!-- et finissent par -->.

<!-- Ceci est un commentaire, invisible sur la page -->

<!-- Navigation principale -->
<nav>
  <a href="/">Accueil</a>
  <!-- <a href="/ancien">Ancienne page</a> -->  <!-- désactivé -->
</nav>
Attention : Les commentaires HTML sont visibles dans le code source de la page (clic droit → Afficher le code source). Ne mettez jamais d'informations sensibles dans un commentaire.
🎯

Atelier — Quiz

🎯 Quiz : Introduction au HTML

🔒

Atelier réservé aux membres

🔤

Les titres : h1 à h6

Il y a 6 niveaux de titres. <h1> est le titre principal, il doit être unique par page. Les niveaux h2 à h6 sont des sous-titres imbriqués. Utilisez-les dans l'ordre logique, pas pour leur taille (c'est le rôle du CSS).

<h1>Titre principal de la page</h1>
<h2>Chapitre 1</h2>
<h3>Section 1.1</h3>
<h4>Sous-section</h4>
<h5>Détail</h5>
<h6>Note fine</h6>
Les titres ne sont pas là pour la taille du texte. C'est le rôle du CSS. Sauter du h1 au h4 est une mauvaise pratique pour le SEO et l'accessibilité.

Paragraphes, br et hr

La balise <p> est la plus courante du HTML. Elle entoure chaque bloc de texte. Important : les sauts de ligne que tu fais dans ton éditeur sont ignorés par le navigateur. Seules les balises définissent la mise en page.

<p>Un paragraphe de texte. Le navigateur ajoute un espace avant et après.</p>

<p>Même si tu mets des sauts de ligne dans le code HTML,
le navigateur les ignorera, seules les balises comptent.</p>

<br>  <!-- saut de ligne forcé (auto-fermante) -->

<hr>  <!-- ligne de séparation horizontale (auto-fermante) -->
<br> s'utilise pour les adresses postales ou les poèmes, pas pour créer de l'espace entre les blocs (utilise margin en CSS pour ça).
✍️

Mise en forme sémantique

HTML propose des balises pour mettre en valeur certains mots ou passages, chacune avec une signification précise. La différence entre <strong> et <b> (ou <em> et <i>) peut sembler cosmétique, elle est en réalité sémantique : <strong> signifie "important", <b> signifie juste "gras". Les moteurs de recherche et lecteurs d'écran font la différence.

BaliseRenduSignification sémantique
<strong>GrasContenu important
<em>ItaliqueEmphase, stress oral
<code>monospaceCode informatique inline
<pre>PréformatéBloc de code, espaces préservés
<mark>SurlignéTexte mis en valeur
<del>BarréContenu supprimé
<small>Plus petitNote, mention légale
<span>AucunConteneur inline neutre (pour CSS)
<p>Cette fonctionnalité est <strong>obligatoire</strong> et <em>très importante</em>.</p>
<p>Utilisez <code>npm install</code> pour installer les dépendances.</p>
<p>Prix : <del>49€</del> <strong>29€</strong> <small>(TVA incluse)</small></p>
<strong> vs <b> : les deux affichent du gras, mais <strong> signifie que c'est sémantiquement important. Préfère toujours <strong> et <em> à <b> et <i>.
🎯

Atelier — Éditeur de code

✏️ Fiddle : Balises de texte

🔒

Atelier réservé aux membres

📋 Crée une fiche de présentation : un <h1> avec ton prénom, un <h2> "À propos", puis un <p> contenant un mot en gras avec <strong> et un mot en italique avec <em>.

HTML
Résultat
🔗

Les liens avec <a>

Les liens sont la base du web : sans eux, chaque page est une île. Ils permettent à l'utilisateur de naviguer entre les pages d'un site, d'ouvrir des ressources externes, ou de sauter directement à une section de la page courante. C'est d'ailleurs pour ça qu'on parle de HyperText dans HTML.

La balise <a> (anchor) crée un lien cliquable. L'attribut href contient la destination.

<!-- Lien externe (autre site) -->
<a href="https://google.com" target="_blank" rel="noopener">Ouvrir Google</a>

<!-- Lien interne (même site) -->
<a href="/contact">Page de contact</a>
<a href="about.html">À propos</a>

<!-- Ancre (même page) -->
<a href="#section-tarifs">Voir les tarifs ↓</a>
<section id="section-tarifs">...</section>

<!-- Lien email et téléphone -->
<a href="mailto:hello@exemple.com">Nous écrire</a>
<a href="tel:+33612345678">Appeler</a>
target="_blank" : ouvre dans un nouvel onglet. Toujours associer rel="noopener" pour des raisons de sécurité : sinon la page ouverte peut accéder à la fenêtre d'origine via JavaScript.
🖼️

Les images avec <img>

Les images donnent du sens et de la vie à une page : une photo illustre un article, un logo identifie un site, une icône aide à la navigation. La balise <img> ne contient pas l'image elle-même, elle pointe vers elle via son chemin ou son URL.

<img> est une balise auto-fermante. Les attributs src et alt sont indispensables.

<!-- Image locale -->
<img src="images/photo.jpg" alt="Photo de l'équipe" width="400">

<!-- Image cliquable (image dans un lien) -->
<a href="galerie.html">
  <img src="apercu.jpg" alt="Voir la galerie complète">
</a>

<!-- Image décorative (alt vide = ignorée par les lecteurs d'écran) -->
<img src="decoration.png" alt="">
alt est obligatoire pour l'accessibilité et le SEO. Si l'image ne charge pas, son texte alt s'affiche à la place. Une image purement décorative peut avoir alt="".
🎯

Atelier — Quiz

🎯 Quiz : Liens & Images

🔒

Atelier réservé aux membres

Listes non ordonnées avec <ul>

<ul> crée une liste à puces. Chaque item est un <li>. On peut imbriquer des listes à l'intérieur.

<ul>
  <li>HTML (la structure)</li>
  <li>CSS (l'apparence)</li>
  <li>JavaScript (le comportement)
    <ul>
      <li>Node.js</li>
      <li>React</li>
    </ul>
  </li>
</ul>
🔢

Listes ordonnées avec <ol>

<ol> génère une liste numérotée. Utile pour des étapes, des classements, des instructions séquentielles.

<ol>
  <li>Créer le fichier index.html</li>
  <li>Écrire le HTML de base</li>
  <li>Ouvrir dans le navigateur</li>
</ol>

<!-- Numérotation personnalisée -->
<ol start="5">
  <li>Cinquième étape</li>
  <li>Sixième étape</li>
</ol>
📊

Tableaux avec <table>

Un tableau se compose de lignes (<tr>), de cellules (<td>) et de cellules d'en-tête (<th>). On structure avec <thead> et <tbody> pour l'accessibilité.

<table>
  <thead>
    <tr>
      <th>Prénom</th>
      <th>Âge</th>
      <th>Ville</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Alice</td>
      <td>28</td>
      <td>Paris</td>
    </tr>
    <tr>
      <td>Bob</td>
      <td>32</td>
      <td>Lyon</td>
    </tr>
  </tbody>
</table>
Les tableaux sont pour les données tabulaires, pas pour la mise en page. Pour organiser des colonnes, utilise Flexbox ou Grid (chapitres CSS plus loin).
🎯

Atelier — Éditeur de code

✏️ Fiddle : Listes & Tableaux

🔒

Atelier réservé aux membres

📋 Crée une liste non ordonnée de tes 3 langages préférés (<ul> + <li>), puis un tableau (<table>) avec 3 colonnes : Langage, Année, Usage, avec au moins 2 lignes de données.

HTML
Résultat
📋

La balise <form>

La balise <form> est le conteneur qui englobe tout le formulaire : champs, labels et bouton d'envoi doivent tous se trouver à l'intérieur. Sans elle, rien ne peut être soumis. L'attribut action indique l'URL vers laquelle les données sont envoyées, et on utilise toujours method="POST" pour l'instant.

<form action="/connexion" method="POST">
  <!-- Les champs ici -->
  <button type="submit">Envoyer</button>
</form>
On verra la différence entre GET et POST dans le chapitre Backend, quand on construira une vraie API.
✏️

Les champs avec <input>

Le <label> est le texte qui indique à l'utilisateur ce qu'il doit saisir dans le champ. En reliant le for du label à l'id du champ, cliquer sur le label place directement le curseur dans le champ — pratique sur mobile et indispensable pour l'accessibilité : les lecteurs d'écran lisent le label pour annoncer à quoi sert chaque champ.

<input> est auto-fermante. Son comportement change selon l'attribut type.

<label for="email">Adresse email :</label>
<input type="email" id="email" name="email" placeholder="vous@exemple.com" required>

<label for="mdp">Mot de passe :</label>
<input type="password" id="mdp" name="password" minlength="8" required>

<label for="age">Âge :</label>
<input type="number" id="age" name="age" min="1" max="120">
TypeUsage
textChamp texte libre
emailValide le format email
passwordMasque les caractères
numberChiffres avec flèches
checkboxCase à cocher (oui/non)
radioChoix unique parmi plusieurs
fileSélecteur de fichier
dateSélecteur de date
📝

Autres éléments de formulaire

<!-- Zone de texte multi-lignes -->
<label for="msg">Message :</label>
<textarea id="msg" name="message" rows="5" placeholder="Votre message..."></textarea>

<!-- Liste déroulante -->
<label for="pays">Pays :</label>
<select id="pays" name="pays">
  <option value="fr">France</option>
  <option value="ch">Suisse</option>
  <option value="be">Belgique</option>
</select>

<!-- Case à cocher -->
<input type="checkbox" id="cgu" name="cgu" required>
<label for="cgu">J'accepte les conditions</label>

<!-- Boutons radio -->
<input type="radio" id="h" name="genre" value="h">
<label for="h">Homme</label>
<input type="radio" id="f" name="genre" value="f">
<label for="f">Femme</label>
Toujours associer un <label> à son champ via for = id. Cela améliore l'accessibilité (clic sur le label = focus sur le champ).
🎯

Atelier — Quiz

🎯 Quiz : Formulaires

🔒

Atelier réservé aux membres

💡

Pourquoi la sémantique ?

Avant HTML5, la quasi-totalité de la structure d'une page se faisait avec des <div>. Un <div> est un conteneur bloc générique : il regroupe des éléments sans leur donner aucune signification particulière. C'est une boîte neutre, utile pour appliquer du style CSS, mais qui ne dit rien sur ce qu'elle contient.

Techniquement, tu pourrais encore tout mettre dans des <div> aujourd'hui, ça fonctionnerait visuellement. Mais un moteur de recherche ne saurait pas ce qui est la navigation, ce qui est le contenu principal. Les balises sémantiques transmettent cette information.

Bon pour le SEO : Google et les autres moteurs comprennent mieux une page bien structurée, ce qui améliore son référencement naturel.
🗂️

Les balises structurelles HTML5

<body>

  <header>           <!-- En-tête : logo, nav principale -->
    <nav>
      <a href="/">Accueil</a>
      <a href="/blog">Blog</a>
    </nav>
  </header>

  <main>             <!-- Contenu principal (unique par page) -->
    <article>        <!-- Contenu autonome (article de blog...) -->
      <h1>Titre de l'article</h1>
      <section>      <!-- Section thématique -->
        <h2>Chapitre 1</h2>
        <p>Contenu...</p>
      </section>
    </article>
    <aside>          <!-- Contenu secondaire (sidebar) -->
      <p>Articles liés</p>
    </aside>
  </main>

  <footer>           <!-- Pied de page -->
    <p>&copy; 2026</p>
  </footer>

</body>
BaliseRôle
<header>En-tête de la page ou d'une section
<nav>Navigation principale du site
<main>Contenu principal, unique par page
<article>Contenu autonome (article, post)
<section>Section thématique d'une page
<aside>Contenu secondaire, sidebar
<footer>Pied de page
📦

div et span : conteneurs génériques

Quand aucune balise sémantique ne correspond, on utilise <div> (bloc) et <span> (inline) comme conteneurs neutres pour appliquer du style CSS.

<div class="card">           <!-- bloc : prend toute la largeur -->
  <span class="badge">Nouveau</span>  <!-- inline : reste dans le flux -->
  <p>Contenu de la carte</p>
</div>
🎯

Atelier — Quiz

🎯 Quiz : Structure sémantique

🔒

Atelier réservé aux membres

💡

HTML vs CSS : qui fait quoi ?

HTML structure le contenu. CSS le met en forme. Cette séparation est fondamentale : si tu veux changer la couleur de tous tes titres, tu modifies une seule règle CSS plutôt que d'ajouter un attribut sur chaque balise HTML.

Analogie : HTML, c'est la charpente d'une maison. CSS, c'est la peinture, le parquet, l'agencement. Tu peux changer toute la décoration sans toucher à la structure.
🔗

3 façons d'écrire du CSS

Il existe trois façons d'appliquer du CSS à une page HTML. Dans la pratique, seule la première est recommandée pour les vrais projets : elle sépare proprement le code de la présentation et permet de partager les styles entre toutes les pages.

MéthodeSyntaxeRecommandé ?
Fichier externe<link rel="stylesheet" href="style.css">✅ Oui, meilleure pratique
Balise <style><style> h1 { color: red; } </style>⚠️ Pour petits tests
Attribut style<h1 style="color:red">❌ À éviter (difficile à maintenir)
<!-- Dans le <head> de ton HTML -->
<link rel="stylesheet" href="style.css">
📝

La syntaxe CSS

Une règle CSS est composée d'un sélecteur (qui cibler) et d'un bloc de déclarations (quoi appliquer). Chaque déclaration est une paire propriété: valeur;.

/* Sélecteur { propriété: valeur; } */

h1 {
  color: #7c3aed;        /* couleur du texte */
  font-size: 2rem;       /* taille de police */
  margin-bottom: 1rem;   /* espace en dessous */
}

p {
  color: #555;
  line-height: 1.6;
}

/* Les commentaires CSS s'écrivent ainsi */
🎯

Atelier — Quiz

🎯 Quiz : Introduction au CSS

🔒

Atelier réservé aux membres

🎨

Les couleurs en CSS

CSS accepte plusieurs formats. On les utilise pour color (couleur du texte) et background-color (couleur de fond).

/* Nom de couleur (anglais) */
h1 { color: red; }
body { background-color: white; }

/* Hexadécimal : #RRGGBB */
h1 { color: #7c3aed; }        /* violet */
h2 { color: #1e40af; }        /* bleu foncé */

/* RGB */
p { color: rgb(100, 100, 100); }

/* RGBA : avec transparence (0 = transparent, 1 = opaque) */
.overlay { background-color: rgba(0, 0, 0, 0.5); }

/* HSL : teinte, saturation, luminosité */
a { color: hsl(262, 83%, 58%); }
Outil pratique : VS Code affiche un carré de couleur cliquable à côté de chaque valeur CSS : tu peux ouvrir un sélecteur de couleur directement dans l'éditeur.
🔤

La typographie

La typographie est souvent la première chose qu'on remarque sur une page, avant même les couleurs. CSS donne un contrôle total sur la police, la taille, le poids, l'interligne et l'alignement. Voici les propriétés les plus utilisées au quotidien.

PropriétéValeurs courantesEffet
font-family'Arial', sans-serifPolice de caractère
font-size16px, 1rem, 1.2emTaille du texte
font-weightnormal, bold, 700Graisse de la police
font-stylenormal, italicItalique
text-alignleft, center, rightAlignement
text-decorationnone, underlineSoulignement
line-height1.5, 1.6Interligne
letter-spacing0.05emEspacement lettres
body {
  font-family: 'Segoe UI', Arial, sans-serif;
  font-size: 16px;
  line-height: 1.6;
  color: #333;
}

h1 {
  font-size: 2.5rem;
  font-weight: 700;
  text-align: center;
}

a {
  text-decoration: none;  /* supprime le soulignement par défaut */
  color: #7c3aed;
}
📐

Unités CSS : px, rem, em, %

Chaque valeur de taille en CSS doit être accompagnée d'une unité. Le choix de l'unité impacte directement l'accessibilité et le comportement responsive de la page. Les unités relatives (rem, %, vw) s'adaptent au contexte ; les unités fixes (px) sont absolues.

UnitéSignificationUtilisation
pxPixels, fixeBordures, petits détails
remRelatif à la taille racine (body)Texte, espacements (recommandé)
emRelatif au parent directEspacements contextuels
%Relatif au parentLargeurs, layouts
vw / vh% de la fenêtre (viewport)Sections plein-écran
Préfère rem pour les tailles de texte : si l'utilisateur change la taille du navigateur, tout s'adapte. 1rem = 16px par défaut.
🎯

Atelier — Quiz

🎯 Quiz : Couleurs & Typographie

🔒

Atelier réservé aux membres

🎯

Les sélecteurs de base

Un sélecteur CSS indique à qui s'appliquent les déclarations. On peut cibler une balise HTML, une classe, un identifiant unique, ou combiner plusieurs critères. Maîtriser les sélecteurs, c'est pouvoir styliser exactement l'élément voulu sans effet de bord.

Pour cibler un élément par classe ou par id, il faut d'abord les déclarer dans le HTML avec les attributs class et id :

<!-- Dans le HTML : on attribue une classe et/ou un id -->
<button class="btn">Envoyer</button>
<nav id="nav-principal">...</nav>

<!-- Plusieurs éléments peuvent avoir la même classe -->
<p class="intro">Premier paragraphe</p>
<p class="intro">Deuxième paragraphe</p>
/* Dans le CSS : . cible une classe, # cible un id */
.btn { background: #7c3aed; }        /* tous les éléments class="btn" */
#nav-principal { position: sticky; } /* l'unique élément id="nav-principal" */
.intro { font-size: 1.1rem; }        /* les deux paragraphes d'un coup */
SélecteurCibleExemple
h1Toutes les balises h1h1 { color: red; }
.ma-classeÉléments avec class="ma-classe".btn { padding: 8px; }
#mon-idL'élément avec id="mon-id"#nav { background: #fff; }
*Tous les éléments* { box-sizing: border-box; }
a:hoverLiens au survol de la sourisa:hover { color: blue; }
p > strongstrong enfant direct d'un pp > strong { color: red; }
/* Classe : réutilisable sur plusieurs éléments */
.bouton {
  background: #7c3aed;
  color: white;
  padding: 0.5rem 1rem;
  border-radius: 6px;
}

/* Pseudo-classe : au survol */
.bouton:hover {
  background: #6d28d9;
}
Classe vs ID : Utilise quasi toujours des classes (réutilisables). Réserve les IDs pour les ancres de navigation ou JavaScript.
📦

Le modèle de boîte (Box Model)

Chaque élément HTML est une boîte rectangulaire composée de 4 zones concentriques :

/*
   ┌──── margin (espace extérieur) ─────┐
   │  ┌─── border (bordure) ────────┐   │
   │  │  ┌── padding (espace int.) ─┐│  │
   │  │  │    content               ││  │
   │  │  └──────────────────────────┘│  │
   │  └──────────────────────────────┘  │
   └────────────────────────────────────┘
*/

.carte {
  width: 300px;           /* largeur du contenu */
  padding: 1.5rem;        /* espace intérieur */
  border: 1px solid #ccc; /* bordure */
  margin: 1rem;           /* espace extérieur */
  border-radius: 8px;     /* coins arrondis */
}

/* Astuce essentielle */
* { box-sizing: border-box; }
Par défaut, width ne compte pas le padding ni la bordure. Avec box-sizing: border-box, la largeur inclut tout, c'est presque toujours ce qu'on veut.
🎯

Atelier — Quiz

🎯 Quiz : Sélecteurs & Box Model

🔒

Atelier réservé aux membres

💡

Le principe de Flexbox

On active Flexbox sur un conteneur (display: flex). Ses enfants directs deviennent des flex items et s'organisent automatiquement selon les règles qu'on définit.

.conteneur {
  display: flex;
  flex-direction: row;      /* row (défaut) | column */
  justify-content: center;  /* alignement axe principal */
  align-items: center;      /* alignement axe secondaire */
  gap: 1rem;                /* espace entre les items */
}
⚖️

justify-content et align-items

justify-content contrôle la distribution des items sur l'axe principal (horizontal par défaut). align-items fait la même chose sur l'axe secondaire (vertical). Ensemble, ils permettent de centrer du contenu facilement. C'était fastidieux en CSS avant Flexbox, ça se fait maintenant en deux lignes.

ValeurEffet (axe horizontal avec flex-direction: row)
flex-startGroupe à gauche (défaut)
flex-endGroupe à droite
centerCentre
space-betweenPremier à gauche, dernier à droite, espace égal entre
space-aroundEspace égal autour de chaque item
/* Centrer du contenu horizontalement ET verticalement */
.hero {
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 100vh;
}

/* Navbar : logo à gauche, liens à droite */
nav {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 1rem 2rem;
}
📊

flex-wrap et flex

Par défaut, les items Flexbox restent sur une seule ligne même s'ils débordent. flex-wrap: wrap autorise le retour à la ligne automatique, indispensable pour les grilles de cartes. La propriété flex sur les items contrôle comment ils grandissent ou rétrécissent pour remplir l'espace disponible.

/* flex-wrap : les items passent à la ligne si pas assez de place */
.grille {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
}

/* flex sur les items */
.item { flex: 1; }              /* grandissent également */
.item-double { flex: 2; }       /* prend 2x plus de place */
.item-fixe { flex: 0 0 200px; } /* largeur fixe à 200px */
🎯

Atelier — Éditeur de code

✏️ Fiddle : Flexbox

🔒

Atelier réservé aux membres

📋 À partir du squelette fourni, ajoute display: flex sur la nav et les cartes, justify-content: space-between sur la nav, et flex-wrap: wrap sur les cartes.

HTML + CSS
Résultat
💡

Comment fonctionne SSH ?

SSH chiffre toute la communication entre votre machine et le serveur. L'authentification peut se faire par mot de passe (pratique mais moins sécurisé) ou par clé SSH (recommandé en production).

Port par défaut : SSH écoute sur le port 22. En production, on le change souvent pour réduire les attaques automatisées.
📦

Installer le serveur SSH

SSH se compose de deux parties : le client (installé par défaut sur presque tous les systèmes) et le serveur (openssh-server), qui doit tourner sur la machine à laquelle on veut se connecter. Sur un Ubuntu Desktop frais, le serveur SSH n'est pas installé — sur un Ubuntu Server, il l'est généralement.

# Vérifier si le serveur SSH est déjà installé et actif
sudo systemctl status ssh

# Si ce n'est pas le cas, l'installer
sudo apt update
sudo apt install openssh-server

# Le service démarre automatiquement après l'installation
# Pour l'activer définitivement au démarrage :
sudo systemctl enable ssh
sudo systemctl start ssh

# Vérifier que SSH écoute bien sur le port 22
ss -tlnp | grep :22           # doit afficher LISTEN sur 0.0.0.0:22
Client SSH : sur Linux/Mac, le client est déjà installé — tape simplement ssh dans le terminal. Sur Windows, utilise le terminal intégré (Windows 10+), PuTTY, ou WSL.
⌨️

Se connecter en SSH

ssh user@192.168.1.10              # connexion par IP
ssh user@monserveur.com            # connexion par domaine
ssh -p 2222 user@monserveur.com   # port non standard
ssh -i ~/.ssh/ma_cle user@serveur  # clé SSH spécifique

# Copier des fichiers via SSH
scp fichier.txt user@serveur:/home/user/
scp -r dossier/ user@serveur:~/     # récursif
rsync -avz dossier/ user@serveur:~/ # sync efficace
🔐

Clés SSH — l'authentification sans mot de passe

Une paire de clés SSH est composée d'une clé privée (sur votre machine, jamais partagée) et d'une clé publique (copiée sur le serveur). Le serveur vérifie que vous possédez la clé privée correspondante.

# 1. Générer une paire de clés (sur votre machine locale)
ssh-keygen -t ed25519 -C "mon@email.com"
# Crée : ~/.ssh/id_ed25519 (privée) et ~/.ssh/id_ed25519.pub (publique)

# 2. Copier la clé publique sur le serveur
ssh-copy-id user@serveur
# Ou manuellement :
cat ~/.ssh/id_ed25519.pub | ssh user@serveur "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys"

# 3. Se connecter sans mot de passe
ssh user@serveur
⚙️

Configurer SSH (sshd_config)

# Editer la configuration du serveur SSH
sudo nano /etc/ssh/sshd_config

# Paramètres recommandés en production :
Port 2222                    # changer le port par défaut
PermitRootLogin no           # interdire connexion root directe
PasswordAuthentication no    # forcer les clés SSH
AllowUsers alice bob         # autoriser seulement ces utilisateurs

# Redémarrer SSH après modification
sudo systemctl restart ssh
Attention : Désactivez l'authentification par mot de passe (PasswordAuthentication no) seulement après avoir vérifié que votre clé SSH fonctionne — sinon vous risquez de vous bloquer hors du serveur.
🖥️

Lab — Pratique interactive

🔒

Atelier réservé aux membres

Connectez-vous pour accéder aux ateliers interactifs.

🖥️ Lab — SSH (4 défis)
bash — user@ubuntu: ~
📦

Installation et démarrage

sudo apt update
sudo apt install nginx

# Nginx démarre automatiquement. Vérifier le statut :
sudo systemctl status nginx
sudo systemctl start nginx
sudo systemctl stop nginx
sudo systemctl restart nginx
sudo systemctl reload nginx  # recharge la config sans coupure
sudo systemctl enable nginx  # démarre automatiquement au boot
⚙️

Servir un site statique

Chaque site géré par Nginx est décrit dans un fichier de configuration (server block) dans /etc/nginx/sites-available/.

# Créer un fichier de config
sudo nano /etc/nginx/sites-available/monsite

# Contenu du fichier :
server {
    listen 80;
    server_name monsite.com www.monsite.com;

    root /var/www/monsite;   # dossier des fichiers
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }
}

# Activer le site (créer un lien symbolique)
sudo ln -s /etc/nginx/sites-available/monsite /etc/nginx/sites-enabled/

# Vérifier la syntaxe de la config
sudo nginx -t

# Recharger Nginx
sudo systemctl reload nginx
🔄

Reverse proxy (Node.js, etc.)

Nginx est souvent utilisé comme reverse proxy : il reçoit les requêtes HTTP et les transmet à une application (Node.js, Python, etc.) qui tourne sur un port local.

server {
    listen 80;
    server_name api.monsite.com;

    location / {
        proxy_pass http://localhost:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }
}
🖥️

Lab — Pratique interactive

🔒

Atelier réservé aux membres

Connectez-vous pour accéder aux ateliers interactifs.

🖥️ Lab — Nginx (4 défis)
bash — user@ubuntu: ~
💡

Pourquoi un pare-feu ?

Un serveur exposé sur Internet est constamment scanné par des robots qui cherchent des ports ouverts pour exploiter des vulnérabilités. Le pare-feu bloque toutes les connexions entrantes par défaut, sauf celles que vous autorisez explicitement.

Autorisez SSH avant d'activer ufw ! Si vous activez ufw sans autoriser SSH, vous serez déconnecté et ne pourrez plus vous reconnecter au serveur.
⌨️

Commandes essentielles

# Voir le statut
sudo ufw status verbose

# Autoriser SSH AVANT d'activer ufw !
sudo ufw allow ssh                 # = allow 22/tcp
sudo ufw allow 2222/tcp            # si SSH sur port custom

# Autoriser des services courants
sudo ufw allow 80/tcp              # HTTP
sudo ufw allow 443/tcp             # HTTPS
sudo ufw allow 'Nginx Full'        # HTTP + HTTPS (profil Nginx)

# Bloquer un port
sudo ufw deny 3306                 # MySQL non exposé

# Activer le pare-feu
sudo ufw enable

# Désactiver
sudo ufw disable

# Supprimer une règle
sudo ufw delete allow 80/tcp
📋

Règles par défaut recommandées

# Configuration typique pour un serveur web
sudo ufw default deny incoming    # bloquer tout par défaut
sudo ufw default allow outgoing   # autoriser les sorties
sudo ufw allow ssh                # SSH
sudo ufw allow 'Nginx Full'       # web
sudo ufw enable

# Vérification
sudo ufw status numbered


# Résultat attendu :
# [ 1] 22/tcp   ALLOW IN   Anywhere
# [ 2] 80/tcp   ALLOW IN   Anywhere
# [ 3] 443/tcp  ALLOW IN   Anywhere
🖥️

Lab — Pratique interactive

🔒

Atelier réservé aux membres

Connectez-vous pour accéder aux ateliers interactifs.

🖥️ Lab — Firewall UFW (4 défis)
bash — user@ubuntu: ~
💡

Comment fonctionne DHCP ?

Quand une machine démarre, elle envoie un broadcast DHCPDISCOVER. Le serveur répond avec un DHCPOFFER (IP proposée), la machine accepte avec un DHCPREQUEST, et le serveur confirme avec un DHCPACK. Tout ça en quelques millisecondes.

ConceptExplication
PoolPlage d'adresses distribuables (ex: 192.168.1.100–200)
Bail (lease)Durée pendant laquelle l'IP est réservée pour la machine
RéservationIP fixe liée à une adresse MAC — comme du statique mais géré par DHCP
OptionsParamètres distribués en plus : gateway, DNS, NTP…
📦

Installation et démarrage

sudo apt install kea                      # installe Kea (DHCPv4 + DHCPv6 + DNS-DDNS)

sudo systemctl start kea-dhcp4-server    # démarre le service DHCPv4
sudo systemctl enable kea-dhcp4-server   # active au démarrage
sudo systemctl status kea-dhcp4-server   # vérifie l'état

# Outil de contrôle propre à Kea
sudo keactrl status                       # état de tous les composants Kea
sudo keactrl reload                       # recharge la config sans redémarrer
⚙️

Configuration — /etc/kea/kea-dhcp4.conf

Kea se configure en JSON. Voici une configuration minimale pour distribuer des IPs sur un réseau 192.168.1.0/24 :

{
  "Dhcp4": {
    "interfaces-config": {
      "interfaces": ["eth0"]               // interface d'écoute
    },
    "lease-database": {
      "type": "memfile",
      "persist": true,
      "name": "/var/lib/kea/dhcp4.leases" // fichier des baux
    },
    "subnet4": [{
      "subnet": "192.168.1.0/24",
      "pools": [{"pool": "192.168.1.100 - 192.168.1.200"}],
      "option-data": [
        {"name": "routers",            "data": "192.168.1.1"},
        {"name": "domain-name-servers","data": "8.8.8.8, 8.8.4.4"},
        {"name": "domain-name",        "data": "exemple.local"}
      ]
    }]
  }
}

Réservations d'adresses (IP fixe par MAC)

"reservations": [{
  "hw-address": "aa:bb:cc:dd:ee:ff",
  "ip-address":  "192.168.1.50",
  "hostname":    "serveur-web"
}]
Kea vs ISC DHCP : L'ancien serveur isc-dhcp-server est en fin de vie depuis 2022. Préfère Kea pour tout nouveau déploiement. La config JSON est plus lisible et peut être modifiée via API REST sans redémarrage.
🖥️

Lab — Pratique interactive

🔒

Atelier réservé aux membres

Connectez-vous pour accéder aux ateliers interactifs.

🖥️ Lab — DHCP Kea (4 défis)
bash — user@ubuntu: ~
🌐

C'est quoi le DNS ?

Les ordinateurs communiquent via des adresses IP : 142.250.74.46. Les humains, eux, retiennent des noms : google.com. Le DNS (Domain Name System) est le système mondial qui fait cette traduction — c'est l'annuaire d'internet.

Quand tu tapes www.google.com dans ton navigateur, avant même de charger la page, ton ordinateur envoie une requête DNS pour demander : "Quelle est l'IP de ce nom ?". Ce n'est qu'une fois la réponse reçue que la connexion commence.

Tu tapes : www.google.com
                │
                ▼  requête DNS
       "Quelle est l'IP de www.google.com ?"
                │
                ▼  réponse DNS
              "142.250.74.46"
                │
                ▼
       Navigateur → connexion HTTP vers 142.250.74.46
Le DNS est hiérarchique et distribué : aucun serveur ne connaît tous les domaines du monde. La résolution se fait en chaîne — racine → TLD → autoritaire. Voir la section Récursion plus bas.
🔧

C'est quoi Bind9 ?

Bind9 (Berkeley Internet Name Domain v9) est le serveur DNS open source le plus déployé au monde. Maintenu par l'ISC (Internet Systems Consortium), il tourne sur une large partie de l'infrastructure DNS mondiale.

On l'installe en réseau interne pour deux raisons :

  • Zones locales — résoudre des noms qui n'existent pas sur internet : srv-prod.monentreprise.local192.168.1.10
  • Centralisation — un seul serveur DNS pour tout le réseau, avec mise en cache et contrôle des accès
Bind9 ≠ résolveur client : Bind9 est un serveur. Le fichier /etc/resolv.conf sur chaque machine configure quel serveur DNS le client interroge — ce sont deux choses distinctes.
📊

Comment Bind9 traite une requête

À chaque requête entrante, Bind9 pose une seule question : est-ce que j'ai autorité sur cette zone ? Sa réponse détermine tout le comportement qui suit.

             Requête DNS
                  │
                  ▼
             Serveur Bind9
                  │
          ┌───────┴────────┐
          │                │
      Zone gérée       Zone non gérée
      (locale)         (internet, etc.)
          │                │
          ▼                ▼
     AUTORITAIRE       Que faire ?
     (répond direct)        │
                ┌───────────┼───────────┐
                │           │           │
                ▼           ▼           ▼
             Forward     Récursion    Refus
           (délègue à   (remonte la  (répond
           un autre DNS) hiérarchie)  REFUSED)
⚙️

Les 4 comportements de Bind9

ModeCe que ça signifieQuand l'utiliserConfig
Autoritaire Bind9 possède le fichier de zone et répond directement, sans interroger personne d'autre. Pour des domaines purement internes qui n'existent pas sur internet : monentreprise.local, srv-prod.interne. Aucun serveur externe ne peut les résoudre à ta place. Zone déclarée dans named.conf.local
Forward Bind9 transfère la requête à un autre serveur DNS (ex: 8.8.8.8) et renvoie sa réponse au client. Il ne cherche pas lui-même. Pour tout ce qui est sur internet (google.com, apt.ubuntu.com…). Plus simple et plus rapide que la récursion complète : tu délègues à un résolveur externe qui a déjà tout en cache. forwarders { 8.8.8.8; 1.1.1.1; };
Récursion Bind9 remonte lui-même la hiérarchie DNS : serveurs racine → TLD → autoritaire. Il trouve la réponse sans dépendre d'un tiers. Si tu veux une indépendance totale vis-à-vis de Google ou Cloudflare. Plus lent au premier appel (plusieurs aller-retours), mais le cache interne compense ensuite. À restreindre aux clients internes uniquement. recursion yes;
allow-recursion { 192.168.0.0/16; };
Refus Bind9 répond REFUSED pour toute zone qu'il ne gère pas en tant qu'autoritaire. Pour un serveur autoritaire public qui ne doit jamais résoudre pour des inconnus. Sans ça, il pourrait être exploité comme amplificateur dans des attaques DDoS DNS. recursion no;
Recette typique en réseau interne : autoritaire pour monentreprise.local + forward vers 8.8.8.8 pour internet + récursion autorisée uniquement pour le réseau interne. C'est le scénario le plus courant en entreprise et en homelab.
🔁

La récursion : racine → TLD → autoritaire

Quand Bind9 résout www.github.com par récursion, il ne connaît qu'une chose au départ : l'adresse des serveurs racine. Il remonte la hiérarchie étape par étape :

Client → "Quelle est l'IP de www.github.com ?"
   │
   ▼
Résolveur Bind9  (cache vide → doit chercher)
   │
   ├─▶  Serveurs RACINE  (a.root-servers.net … m.root-servers.net)
   │       Rôle : connaître les serveurs de chaque TLD
   │       Réponse : "pour .com → contacte a.gtld-servers.net"
   │
   ├─▶  Serveurs TLD .com  (Verisign — a.gtld-servers.net)
   │       Rôle : connaître les serveurs autoritaires de chaque domaine .com
   │       Réponse : "pour github.com → contacte ns1.p16.dynect.net"
   │
   ├─▶  Serveurs autoritaires GitHub  (ns1.p16.dynect.net)
   │       Rôle : détenir les vrais enregistrements du domaine
   │       Réponse : "www.github.com → 140.82.112.3  TTL: 60s"
   │
   ▼
Résolveur → met en cache 60s → répond au client
Client → connexion vers 140.82.112.3
NiveauQui ?Rôle précis
Racine13 clusters (A–M), ~1 500 instances mondialesNe connaît que les adresses des serveurs TLD. Point de départ obligatoire de toute récursion.
TLDVerisign (.com/.net), AFNIC (.fr), DENIC (.de)…Connaît les serveurs autoritaires pour chaque domaine enregistré sous son extension.
AutoritaireTon registrar (OVH, Gandi…) ou ton propre Bind9Détient les enregistrements réels : A, MX, CNAME, TXT… C'est lui qui donne la réponse finale.
RésolveurTon Bind9, 8.8.8.8, 1.1.1.1…Orchestre toute la récursion et met le résultat en cache pour éviter de recommencer à chaque fois.
Le TTL et le cache : chaque réponse DNS inclut un TTL (Time To Live) en secondes. Pendant cette durée, le résolveur sert la réponse depuis son cache sans refaire le circuit complet. Un TTL court (60 s) permet de changer l'IP rapidement ; un TTL long (86 400 s = 1 jour) réduit la charge sur les serveurs.
📦

Installation et configuration initiale

sudo apt install bind9 bind9utils bind9-doc

sudo systemctl enable --now named   # démarre et active au boot
sudo systemctl status named           # vérifie que le démon tourne

Les fichiers de configuration sont dans /etc/bind/ :

FichierRôle
named.confPoint d'entrée — inclut les 3 fichiers ci-dessous
named.conf.optionsOptions globales : forwarders, récursion, ACL, cache
named.conf.localDéclaration de tes zones personnalisées
/etc/bind/zones/*.dbFichiers de zone contenant les enregistrements DNS

Exemple de named.conf.options — réseau interne

options {
    directory "/var/cache/bind";

    // Forwarder les requêtes internet vers Google et Cloudflare
    forwarders {
        8.8.8.8;
        1.1.1.1;
    };
    forward only;            // ne fait pas de récursion directe, délègue toujours

    // Autoriser la récursion uniquement pour le réseau interne
    recursion yes;
    allow-recursion { 192.168.0.0/16; 127.0.0.1; };

    // Autoriser les requêtes depuis le réseau interne
    allow-query { any; };

    dnssec-validation auto;
    listen-on { any; };
};
forward only vs forward first : avec forward only, si les forwarders ne répondent pas, la requête échoue. Avec forward first, Bind9 tente la récursion directe en secours. En réseau interne stable, forward only est suffisant.
🗂️

Types d'enregistrements DNS

TypeSignificationExemple
SOAStart of Authority — informations sur la zone (serial, TTL, admin)Obligatoire, 1 seul par zone
ANom → adresse IPv4www IN A 192.168.1.20
AAAANom → adresse IPv6www IN AAAA 2001:db8::1
CNAMEAlias → autre nom (pas vers une IP)ftp IN CNAME www
MXServeur de messagerie de la zone@ IN MX 10 mail.exemple.local.
NSServeur de noms autoritaire pour la zone@ IN NS ns1.exemple.local.
PTRIP → Nom (DNS inverse — zone in-addr.arpa)20 IN PTR www.exemple.local.
TXTTexte libre — SPF, DKIM, vérification de domaine@ IN TXT "v=spf1 mx ~all"
Le point final est obligatoire sur les noms complets (FQDN) dans un fichier de zone : mail.exemple.local.. Sans le point, Bind9 ajoute automatiquement le nom de la zone, ce qui donne mail.exemple.local.exemple.local — une erreur silencieuse difficile à repérer.
🗺️

Configurer une zone locale

1. Déclarer la zone dans named.conf.local

zone "exemple.local" {
    type master;
    file "/etc/bind/zones/exemple.local.db";
};

zone "1.168.192.in-addr.arpa" {    // zone inverse pour 192.168.1.0/24
    type master;
    file "/etc/bind/zones/reverse.db";
};

2. Créer le fichier de zone

; /etc/bind/zones/exemple.local.db
$TTL 86400
@   IN  SOA  ns1.exemple.local. admin.exemple.local. (
            2024010101 ; serial   ← date + incrément, à changer à chaque modif
            3600       ; refresh  ← secondaires rechargent toutes les heures
            900        ; retry    ← délai avant réessai si refresh échoue
            604800     ; expire   ← durée max sans contact avec le primaire
            86400 )    ; minimum TTL

@    IN  NS    ns1.exemple.local.
ns1  IN  A     192.168.1.10       ; le serveur DNS lui-même
www  IN  A     192.168.1.20
mail IN  A     192.168.1.30
@    IN  MX 10 mail.exemple.local.
ftp  IN  CNAME www                ; ftp.exemple.local → www.exemple.local

3. Valider et appliquer

sudo named-checkconf                                        # vérifie named.conf
sudo named-checkzone exemple.local /etc/bind/zones/exemple.local.db

sudo systemctl reload named                               # applique sans interruption

4. Tester avec dig

dig @localhost www.exemple.local                 # enregistrement A
dig @localhost exemple.local MX                  # enregistrement MX
dig @localhost -x 192.168.1.20                   # résolution inverse (PTR)
nslookup www.exemple.local localhost               # alternative à dig
Le serial est crucial : à chaque modification du fichier de zone, incrémente le serial. Convention : YYYYMMDDNN (ex: 2024090101). Sans ça, les serveurs secondaires ne rechargent pas la zone et continuent à servir les anciennes données.
🖥️

Lab — Pratique interactive

🔒

Atelier réservé aux membres

Connectez-vous pour accéder aux ateliers interactifs.

🖥️ Lab — DNS Bind9 (5 défis)
bash — user@ubuntu: ~
👤

Informations du compte

U
🎯

Ma progression