Retour au cours

frontend / html

Web Storage et IndexedDB : bases

Leçon 161 exercice

Explication

Ce que vous allez apprendre

  • Choisir entre localStorage et sessionStorage selon la durée de vie voulue
  • Sérialiser correctement un objet avec JSON.stringify/JSON.parse avant de le stocker
  • Comprendre pourquoi l'événement storage ne se déclenche jamais sur l'onglet qui écrit
  • Créer une base IndexedDB avec un object store et un index de recherche
  • Reconnaître les limites d'une transaction IndexedDB (expiration, onupgradeneeded)

Dans quel contexte ?

Un utilisateur se plaint que son thème sombre, choisi sur parametres.html, redevient clair à chaque nouvelle visite. En vérifiant le code, on découvre que la préférence est stockée dans une simple variable JavaScript, perdue au rechargement de la page. En la sauvegardant avec localStorage.setItem("theme", "sombre"), la préférence persiste indéfiniment, même après fermeture complète du navigateur — contrairement à sessionStorage, qui aurait été effacé à la fermeture de l'onglet.

Étape 1 : tout ne passe pas par le serveur

Toutes les données d'une page web ne doivent pas forcément transiter par un serveur distant. Se souvenir qu'un utilisateur préfère le thème sombre, ou garder le brouillon d'un formulaire, sont des besoins que le navigateur peut satisfaire seul.

Étape 2 : l'outil le plus simple, le Storage

Commençons par le plus simple. sessionStorage et localStorage partagent la même API basique (setItem, getItem), mais diffèrent par leur durée de vie : le premier disparaît à la fermeture de l'onglet, le second persiste indéfiniment.

Étape 3 : le piège à connaître avec ces deux outils

Il y a un piège fréquent à connaître tout de suite : ces deux outils ne stockent QUE des chaînes de caractères. Tenter d'y stocker un objet directement échoue silencieusement — il faut d'abord le convertir en texte avec JSON.stringify.

Piège fréquent

localStorage.setItem("prefs", { langue: "fr" }) ne stocke pas l'objet : JavaScript le convertit silencieusement en la chaîne littérale "[object Object]", impossible à relire correctement ensuite. Passez toujours par JSON.stringify() à l'écriture et JSON.parse() à la lecture.

Étape 4 : un outil plus puissant pour plus de données

Quand le Storage ne suffit plus (gros volumes, requêtes complexes), IndexedDB prend le relais : c'est une vraie base de données structurée côté navigateur, avec recherche par index.

Étape 5 : un piège subtil sur l'événement storage

Il reste un comportement contre-intuitif à connaître : beaucoup de développeurs s'attendent à ce que l'événement storage se déclenche dès qu'ils modifient localStorage. En réalité, il ne se déclenche JAMAIS sur l'onglet qui écrit — uniquement sur les AUTRES onglets ouverts sur le même site.

C'est justement ce qui le rend utile : synchroniser un état entre onglets, par exemple déconnecter tous les onglets ouverts quand l'utilisateur se déconnecte dans l'un d'eux.

APIPersistancePartagé entre onglets ?Type de données
localStorageIllimitéeOui (même origine)Chaînes uniquement
sessionStorageOnglet courantNonChaînes uniquement
IndexedDBIllimitéeOui (même origine)Objets structurés, index

Prérequis

Cette leçon suppose une bonne aisance avec les Promises et async/await en JavaScript, indispensables pour manipuler IndexedDB sans code illisible.

Étape 6 : les règles propres à IndexedDB

Enfin, IndexedDB impose deux contraintes strictes. D'abord, la structure de la base (les "object stores") ne peut être créée ou modifiée que dans l'événement onupgradeneeded.

Ensuite, une transaction expire si elle reste inactive trop longtemps : il ne faut donc jamais mélanger un appel réseau au milieu d'une transaction IndexedDB, sous peine de la voir échouer silencieusement.

Et ensuite ?

Une fois le stockage local maîtrisé, la prochaine leçon change complètement de registre : dessiner des formes et animations avec Canvas 2D.

Commandes & code

Web Storage et IndexedDB : bases

Stocker des données côté client, du simple (Storage) au structuré (IndexedDB).

js
// --- localStorage : persiste après fermeture du navigateur ---
localStorage.setItem("theme", "sombre");
localStorage.setItem("preferences", JSON.stringify({ langue: "fr", taille: 14 }));
// setItem ne stocke QUE des chaînes : toujours JSON.stringify les objets

const prefs = JSON.parse(localStorage.getItem("preferences") ?? "{}");
console.log(prefs.langue); // "fr"

localStorage.removeItem("theme");
localStorage.clear(); // vide tout le domaine, à utiliser avec prudence

// --- sessionStorage : identique en API, vidé à la fermeture de l'onglet ---
sessionStorage.setItem("etape-formulaire", "3");
// Non partagé entre onglets, même sur la même origine (contrairement à localStorage)
js
// Écouter les changements de localStorage depuis un AUTRE onglet
// (l'onglet qui écrit ne reçoit jamais son propre événement "storage")
window.addEventListener("storage", (e) => {
    console.log(`${e.key} : "${e.oldValue}" -> "${e.newValue}"`);
    // Utile pour synchroniser un état (ex: déconnexion) entre plusieurs onglets ouverts
});
js
// --- IndexedDB : base de données transactionnelle côté navigateur ---
// API bas niveau, événementielle — on l'enveloppe presque toujours dans des Promises

function ouvrirBase() {
    return new Promise((resolve, reject) => {
        const requete = indexedDB.open("technologik-db", 1);

        requete.onupgradeneeded = (e) => {
            const db = e.target.result;
            // onupgradeneeded : seul endroit où créer/modifier des object stores,
            // déclenché si la version n'existe pas encore ou a augmenté
            if (!db.objectStoreNames.contains("cours")) {
                const store = db.createObjectStore("cours", { keyPath: "id" });
                store.createIndex("par_titre", "titre", { unique: false });
            }
        };

        requete.onsuccess = (e) => resolve(e.target.result);
        requete.onerror = (e) => reject(e.target.error);
    });
}

async function ajouterCours(db, cours) {
    return new Promise((resolve, reject) => {
        const tx = db.transaction("cours", "readwrite");
        // Une transaction expire dès qu'elle reste inactive un "tick" —
        // ne jamais await autre chose qu'IndexedDB à l'intérieur
        const store = tx.objectStore("cours");
        store.put(cours); // put = upsert, add = échoue si la clé existe déjà

        tx.oncomplete = () => resolve();
        tx.onerror = () => reject(tx.error);
    });
}

async function chercherParTitre(db, titre) {
    return new Promise((resolve, reject) => {
        const tx = db.transaction("cours", "readonly");
        const index = tx.objectStore("cours").index("par_titre");
        const requete = index.get(titre);

        requete.onsuccess = () => resolve(requete.result);
        requete.onerror = () => reject(requete.error);
    });
}
js
// Utilisation
const db = await ouvrirBase();
await ajouterCours(db, { id: 1, titre: "HTML", niveau: "expert" });
const resultat = await chercherParTitre(db, "HTML");
console.log(resultat); // { id: 1, titre: "HTML", niveau: "expert" }
APICapacitéPersistanceType de données
localStorage~5-10 Mo, synchroneIllimitéeChaînes uniquement
sessionStorage~5-10 Mo, synchroneOnglet courantChaînes uniquement
IndexedDBPlusieurs centaines de Mo, asynchroneIllimitéeObjets structurés, index, requêtes

Résumé

  • localStorage/sessionStorage ne stockent que des chaînes : toujours sérialiser en JSON.
  • L'événement storage ne se déclenche que sur les AUTRES onglets, jamais sur celui qui écrit.
  • IndexedDB crée/modifie ses object stores uniquement dans onupgradeneeded.
  • Une transaction IndexedDB expire si elle reste inactive : ne jamais await une opération externe au milieu.

Exercices pratiques

1 disponible
1

Mission : rendre permanent le thème sombre de parametres.html

Objectif : Corriger une préférence de thème perdue à chaque rechargement, puis comprendre le comportement de synchronisation entre onglets.

Contexte

Un utilisateur se plaint que son thème sombre, choisi sur parametres.html, redevient clair à chaque nouvelle visite. Le code actuel stocke la préférence dans let theme = "sombre";, une simple variable JavaScript. La page stocke aussi un objet de préférences complet { langue: "fr", theme: "sombre" } qu'il faudra sauvegarder correctement.

Ta mission : rendre cette préférence persistante.

Résoudre l’exercice →