IndexedDB : index, plages et curseurs
IndexedDB n’a pas de langage de requête : on interroge par clé, par index et par plage, et on parcourt avec des curseurs. Les exemples utilisent les magasins clients et commandes (Ouvrir une base et définir le schéma) et les utilitaires requete / terminee (CRUD et transactions).
Lire par index
Section intitulée « Lire par index »async function clientParEmail(email) { const db = await ouvrirBase(); const index = db.transaction('clients').objectStore('clients').index('email'); return requete(index.get(email)); // premier enregistrement correspondant}
async function clientsParNom(nom) { const db = await ouvrirBase(); const index = db.transaction('clients').objectStore('clients').index('nom'); return requete(index.getAll(nom)); // tous les enregistrements avec ce nom}Un index permet get, getAll, getAllKeys, count et openCursor, comme le magasin, mais selon la valeur du champ indexé.
Plages : IDBKeyRange
Section intitulée « Plages : IDBKeyRange »IDBKeyRange.only('Alice'); // égal àIDBKeyRange.lowerBound(18); // >= 18IDBKeyRange.lowerBound(18, true); // > 18IDBKeyRange.upperBound(65); // <= 65IDBKeyRange.upperBound(65, true); // < 65IDBKeyRange.bound(18, 65); // 18 <= x <= 65IDBKeyRange.bound(18, 65, true, false); // 18 < x <= 65async function commandesDuMois(debut, fin) { const db = await ouvrirBase(); const index = db .transaction('commandes') .objectStore('commandes') .index('date'); const plage = IDBKeyRange.bound(debut, fin); // dates : objets Date ou chaînes ISO return requete(index.getAll(plage));}Les chaînes ISO (2026-01-15) se comparent correctement dans l’ordre alphabétique : pratique pour des plages de dates.
Recherche par préfixe
Section intitulée « Recherche par préfixe »function plagePrefixe(prefixe) { return IDBKeyRange.bound(prefixe, prefixe + '');}
const clients = await requete( db .transaction('clients') .objectStore('clients') .index('nom') .getAll(plagePrefixe('Al')),); est le dernier caractère Unicode de ce plan : la plage couvre toutes les chaînes qui commencent par le préfixe. La comparaison est sensible à la casse : stocker une version en minuscules (nomMin) pour une recherche insensible à la casse.
Limiter et paginer
Section intitulée « Limiter et paginer »const premiers = await requete(index.getAll(null, 20)); // 20 premiersconst cles = await requete(index.getAllKeys(plage, 100)); // 100 clésPage suivante : repartir après la dernière valeur reçue (pagination par clé, plus efficace qu’un décalage) :
async function page(dernierNom, taille = 20) { const db = await ouvrirBase(); const index = db.transaction('clients').objectStore('clients').index('nom'); const plage = dernierNom === undefined ? null : IDBKeyRange.lowerBound(dernierNom, true); return requete(index.getAll(plage, taille));}Si plusieurs enregistrements peuvent avoir la même valeur d’index, utiliser un curseur avec la clé primaire (ci-dessous) pour ne pas en sauter.
Curseurs
Section intitulée « Curseurs »Un curseur parcourt les enregistrements un par un, dans l’ordre de la clé ou de l’index :
async function parcourir(magasinOuIndex, rappel) { await new Promise((resolve, reject) => { const req = magasinOuIndex.openCursor(); req.onsuccess = () => { const curseur = req.result; if (!curseur) return resolve(); // fin du parcours rappel(curseur.value, curseur.key); curseur.continue(); }; req.onerror = () => reject(req.error); });}
const db = await ouvrirBase();const tx = db.transaction('clients');await parcourir(tx.objectStore('clients').index('nom'), (client) => console.log(client.nom),);| Élément | Rôle |
|---|---|
openCursor(plage?, direction?) |
Ouvre le curseur (valeurs) |
openKeyCursor |
Curseur sur les clés seulement (plus léger) |
curseur.value, .key, .primaryKey |
Valeur, clé de l’index, clé primaire |
curseur.continue() |
Avance |
curseur.advance(n) |
Saute n enregistrements |
curseur.update(valeur) |
Remplace l’enregistrement courant |
curseur.delete() |
Supprime l’enregistrement courant |
Directions : 'next' (défaut), 'prev' (ordre décroissant), 'nextunique', 'prevunique'.
const req = index.openCursor(null, 'prev'); // du plus grand au plus petitModifier en parcourant
Section intitulée « Modifier en parcourant »async function marquerExpediees(db) { const tx = db.transaction('commandes', 'readwrite'); const index = tx.objectStore('commandes').index('date'); await new Promise((resolve, reject) => { const req = index.openCursor(IDBKeyRange.upperBound('2026-01-01')); req.onsuccess = () => { const c = req.result; if (!c) return resolve(); if (c.value.statut === 'payee') c.update({ ...c.value, statut: 'expediee' }); c.continue(); }; req.onerror = () => reject(req.error); }); await terminee(tx);}Un curseur est plus lent que getAll pour de petits volumes (un aller-retour par enregistrement) mais indispensable pour les gros volumes (mémoire constante) et pour modifier ou supprimer en parcourant.
Itération asynchrone
Section intitulée « Itération asynchrone »Avec une bibliothèque comme idb, les curseurs s’écrivent avec for await (voir Avec idb et Dexie).
Filtrer sur plusieurs champs
Section intitulée « Filtrer sur plusieurs champs »IndexedDB ne combine pas plusieurs index dans une requête. Solutions :
| Approche | Principe |
|---|---|
| Index composé | createIndex('statutEtDate', ['statut', 'date']), puis IDBKeyRange.bound(['payee', debut], ['payee', fin]) |
| Filtrer en JavaScript | Lire par le champ le plus sélectif, filtrer le reste dans le code |
| Dexie | Syntaxe de requête plus riche (voir la page suivante) |
// à la création du schémacommandes.createIndex('statutEtDate', ['statut', 'date']);
// requête : commandes payées en janvierconst plage = IDBKeyRange.bound( ['payee', '2026-01-01'], ['payee', '2026-01-31'],);const resultat = await requete( db .transaction('commandes') .objectStore('commandes') .index('statutEtDate') .getAll(plage),);Les résultats sont triés par la clé de l’index utilisé ; pour un autre tri, créer un index sur ce champ, ou trier en JavaScript après lecture :
const tries = (await requete(store.getAll())).sort((a, b) => b.date.localeCompare(a.date),);Dépannage
Section intitulée « Dépannage »| Symptôme | Piste |
|---|---|
DataError: The parameter is not a valid key |
Plage ou valeur de type différent de la clé indexée (nombre contre chaîne), ou valeur null/booléen |
| Un enregistrement est absent de l’index | Son champ manque ou n’est pas une clé valide |
NotFoundError sur index('x') |
Index non créé (schéma ou version) |
| La pagination saute des éléments | Plusieurs enregistrements ont la même valeur d’index : curseur avec clé primaire |
Mémoire élevée avec getAll |
Limiter, ou parcourir avec un curseur |
TransactionInactiveError pendant un curseur |
await d’une opération étrangère dans le rappel |
| Tri sensible à la casse | Stocker un champ normalisé et l’indexer |
Ubuntu 24.04 et 26.04 : différences à connaître
Section intitulée « Ubuntu 24.04 et 26.04 : différences à connaître »Aucune.
Pour aller plus loin
Section intitulée « Pour aller plus loin »- IDBKeyRange, IDBCursor
- Suite : Avec idb et Dexie