La rapidité joue un rôle crucial dans la na­vi­ga­tion sur le World Wide Web. Personne n’a envie d’attendre une éternité pour pouvoir accéder à une page. Pour que la connexion à la page soit aussi rapide que possible, il est utile de disposer d’une partie des in­for­ma­tions dans le na­vi­ga­teur de l’uti­li­sa­teur et de ne pas avoir à les trans­mettre à nouveau. IndexedDB offre cette pos­si­bi­lité : une mémoire placée di­rec­te­ment dans le na­vi­ga­teur de l’uti­li­sa­teur, à laquelle chaque site Internet peut accéder. Comment cela fonc­tionne-t-il ?

À quoi sert IndexedDB ?

Le fait que les serveurs en­re­gistrent les données des clients, mais aussi que les clients con­ser­vent certaines in­for­ma­tions relatives à un site Internet apparaît logique. Cela permet d’accélérer la na­vi­ga­tion en évitant de charger à nouveau l’in­té­gra­lité des in­for­ma­tions à chaque con­sul­ta­tion. Ce stockage rend également possible l’uti­li­sa­tion d’ap­pli­ca­tions Web hors ligne et les in­for­ma­tions saisies par les uti­li­sa­teurs peuvent très bien être hébergées côté client. Les cookies ont été conçus pré­ci­sé­ment à cette fin. Ces derniers n’ont toutefois qu’une portée très res­treinte en termes de volume de fichiers et d’uti­li­sa­tion, une portée très largement in­suf­fi­sante pour les ap­pli­ca­tions Web modernes. Par ailleurs, les cookies doivent être envoyés via le réseau à chaque con­sul­ta­tion HTTP.

Une première solution s’est présentée avec le stockage Web également appelé fré­quem­ment « DOM Storage » : si cette tech­no­lo­gie est fortement basée sur l’idée des cookies, elle fait toutefois passer le volume de quelques kilo-octets à 10 Mo, un volume une fois encore in­suf­fi­sant. Ces fichiers, que l’on surnomme souvent « su­per­coo­kies », sont struc­tu­rés de façon ex­trê­me­ment simple et ne comporte pas les ca­rac­té­ris­tiques d’une base de données moderne. En raison de leur taille limitée, les cookies et les su­per­coo­kies ne cons­ti­tuent toutefois pas une solution optimale et ne per­met­tent pas d’avoir des données struc­tu­rées et des index, ce qui rend im­pos­sible toute recherche.

Une nouvelle orien­ta­tion nous avait tout d’abord été promise avec le dé­ve­lop­pe­ment de Web SQL, une mémoire côté client basée sur SQL. Mais le World Wide Web Con­sor­tium (W3C), qui a pour but de dé­ve­lop­per des normes Web, a cessé son travail sur cette mémoire au profit d’IndexedDB. Sous l’égide de Mozilla, une norme qui supporte aujourd’hui la plupart des na­vi­ga­teurs modernes a ainsi vu le jour.

Na­vi­ga­teurs supportés par IndexedDB

Chrome Firefox Opera Opera mini Safari IE Edge

Quels sont les avantages des bases IndexedDB ?

Avant toute chose, cette norme est une interface con­fi­gu­rée dans le na­vi­ga­teur per­met­tant aux sites Internet d’en­re­gis­trer des in­for­ma­tions di­rec­te­ment dans ce dernier. Elle s’appuie sur Ja­vaS­cript. Chaque site Internet peut ainsi créer sa propre base de données. Et seul le site Web cor­res­pon­dant peut accéder à la base IndexedDB (abré­via­tion d’Indexed Database API), ga­ran­tis­sant ainsi la sécurité des données. Plusieurs « Object Storages » sont dis­po­nibles dans les bases de données. Là encore, dif­fé­rents formats peuvent y être déposés : chaînes, chiffres, objets, tableaux et dates.

IndexedDB n’est pas une base de données re­la­tion­nelle, mais un système de tableaux indexés. Il s’agit en fait d’une base de données NoSQL, comme par exemple MongoDB. Les entrées sont toujours créées par paires, avec une clé et une valeur. La valeur est un objet et la clé la propriété de cet objet. S’y ajoutent des index per­met­tant une recherche rapide.

Dans IndexedDB, les actions sont toujours ef­fec­tuées sous la forme de tran­sac­tions. Chaque procédure d’écriture, de lecture ou de mo­di­fi­ca­tion est intégrée à une tran­sac­tion afin de garantir l’ap­pli­ca­tion dans leur in­té­gra­lité ou non des mo­di­fi­ca­tions à la base de données. L’un des avantages d’IndexedDB est que, dans la plupart des cas, le transfert des données n’a pas à être effectué de façon syn­chro­ni­sée. Les opé­ra­tions sont ef­fec­tuées de façon asyn­chrone, ce qui permet d’empêcher tout blocage du na­vi­ga­teur Web pendant l’opération et de garantir son uti­li­sa­tion par l’uti­li­sa­teur.

La sécurité joue un rôle crucial dans IndexedDB. Il convient en effet de s’assurer que les sites Internet ne peuvent pas accéder aux bases de données d’autres sites Internet. À cette fin, IndexedDB a établi une Same Origin Policy (« politique de même origine ») : le domaine, le protocole de couche d’ap­pli­ca­tion et le port doivent être iden­tiques, sans quoi les données ne seront pas dis­po­nibles. Dans ce cadre, il est tout à fait possible que des sous-dossiers d’un domaine puissent accéder à la base IndexedDB d’un autre sous-dossier puisque les deux ont la même origine. En revanche, il est im­pos­sible d’y accéder lorsqu’un autre port est utilisé ou lorsque le protocole passe de HTTP à HTTPS ou in­ver­se­ment.

Tutoriel IndexedDB : uti­li­sa­tion de cette tech­no­lo­gie

Nous vous ex­pli­quons IndexedDB par un exemple. Avant de pouvoir créer une base de données et des Object Stores, il convient toutefois de procéder à une vé­ri­fi­ca­tion. Même si aujourd’hui, IndexedDB est com­pa­tible avec tous les na­vi­ga­teurs modernes, ce n’est pas le cas des na­vi­ga­teurs obsolètes. Par con­sé­quent, vérifiez tout d’abord que votre na­vi­ga­teur supporte IndexedDB. Pour ce faire, vérifiez l’objet Window.

Note

Vous pouvez suivre les exemples de code via la console des outils de dé­ve­lop­peur dans le na­vi­ga­teur. Ces outils vous per­met­tent également de consulter les bases IndexedDB d’autres sites.

if (!window.IndexedDB) {
	alert("IndexedDB n’est pas supporté !");
}

Si votre na­vi­ga­teur ne supporte pas IndexedDB, une fenêtre de dialogue vous en informant apparaît. Vous pouvez également générer un message d’erreur dans votre fichier journal avec console.error.

À présent, ouvrons une base de données. En principe, un site Internet peut ouvrir plusieurs bases de données, mais la pratique a montré qu’il était pré­fé­rable de créer une seule IndexedDB par domaine. Cette base de données permettra de tra­vail­ler avec plusieurs Object Stores. L’ouverture d’une base de données fonc­tionne via une requête asyn­chrone.

var request = window.IndexedDB.open("Mabasededonnées", 1);

À l’ouverture, elle indique deux arguments : tout d’abord un nom auto-choisi (sous forme de chaîne) puis un numéro de version (sous forme de nombre entier). On commence lo­gi­que­ment à la version 1. L’objet qui en résulte fournit l’un des trois évé­ne­ments suivants :

  • error : une erreur est survenue lors de la création.
  • up­gra­de­nee­ded : la version de la base de données a été modifiée. Ceci apparaît donc également lors de la création, puisqu’ici aussi le numéro de version passe de non-existant à 1.
  • success : la base de données a été ouverte avec succès.

Il est à présent possible de créer la base de données à pro­pre­ment parler ainsi qu’un Object Store.

request.onupgradeneeded = function(event) {
	var db = event.target.result;
	var objectStore = db.createObjectStore("Nutzer", { keyPath: "id", autoIncrement: true });
}

Notre Object Store contient le nom Uti­li­sa­teur. La clé est id, une nu­mé­ro­ta­tion simple qui ne cessera d’augmenter avec l’au­toIn­cre­ment. Vous pouvez main­te­nant alimenter la base de données ou l’Object Store avec des données. Pour ce faire, créez tout d’abord un ou plusieurs index. Dans notre exemple, nous sou­hai­tons créer un index pour les noms d’uti­li­sa­teurs et un index pour les adresses email utilisées.

objectStore.createIndex("Nickname", "Nickname", { unique: false });
objectStore.createIndex("eMail", "eMail", { unique: true });

Vous pouvez ainsi trouver en toute sim­pli­cité des ensembles de données avec le pseu­do­nyme utilisé par un uti­li­sa­teur ou son adresse email. La dif­fé­rence entre les deux index est que le pseu­do­nyme ne doit pas être attribué une seule fois, en revanche chaque adresse email ne peut comporter qu’une seule entrée.

Vous pouvez main­te­nant créer des entrées. Toutes les opé­ra­tions réalisées avec la base de données doivent être intégrées à une tran­sac­tion. Il en existe trois types :

  • readonly : permet de lire les données d’un Object Store. Plusieurs tran­sac­tions de ce type peuvent se dérouler en parallèle, même si elles se rap­por­tent au même domaine.
  • readwrite : permet de lire et de créer des entrées. Ces tran­sac­tions peuvent uni­que­ment se dérouler en parallèle lorsqu’elles se rap­por­tent à dif­fé­rents domaines.
  • ver­sion­change : procède à des mo­di­fi­ca­tions sur l’Object Store ou les index, mais crée et modifie également les entrées. Ce mode ne peut pas être créé ma­nuel­le­ment et est au­to­ma­ti­que­ment déclenché avec l’événement « up­gra­de­nee­ded ».

Pour créer une nouvelle entrée, on utilise donc « readwrite »

const dbconnect = window.IndexedDB.open('Mabasededonnées', 1);
dbconnect.onupgradeneeded = ev => {
    console.log('Upgrade DB');
    const db = ev.target.result;
    const store = db.createObjectStore('utilisateur', { keyPath: 'id', autoIncrement: true });
    store.createIndex('Nickname', 'Nickname', { unique: false });
    store.createIndex('eMail', 'eMail', { unique: true });
}
dbconnect.onsuccess = ev => {
    console.log('mise à jour DB avec succès');
    const db = ev.target.result;
    const transaction = db.transaction('Utilisateur', 'readwrite');
    const store = transaction.objectStore('Utilisateur');
    const data = [
        {Nickname: 'Raptor123', eMail: 'raptor@example.com'},
        {Nickname: 'Dino2', eMail: 'dino@example.com'}
    ];
    data.forEach(el => store.add(el));
    transaction.onerror = ev => {
        console.error('Une erreur est survenue!', ev.target.error.message);
    };
    transaction.oncomplete = ev => {
        console.log('Les données ont été ajoutées avec succès !');
        const store = db.transaction('Utilisateur', 'readonly').objectStore('Utilisateur');
        //const query = store.get(1); // Requête indivuelle
        const query = store.openCursor()
        query.onerror = ev => {
            console.error('Echec de la requête !', ev.target.error.message);
        };
        /*
        // Traitement de la requête individuelle
        query.onsuccess = ev => {
            if (query.result) {
                console.log('Ensemble de données , query.result.Nickname, query.result.eMail);
            } else {
                console.warn(Aucune entrée disponible !');
            }
        };
        */
        query.onsuccess = ev => {
            const cursor = ev.target.result;
            if (cursor) {
                console.log(cursor.key, cursor.value.Nickname, cursor.value.eMail);
                cursor.continue();
            } else {
                console.log('Plus d’entrées disponibles');
            }
        };
    };
};

Cette fonction permet d’ajouter des in­for­ma­tions à votre Object Store. Vous pouvez également afficher des messages via la console en fonction du succès de la tran­sac­tion. En règle générale, on souhaite également lire les données que l’on a placées dans une base IndexedDB. Pour ce faire, on utilise get.

var transaction = db.transaction(["Utilisateur"]);
var objectStore = transaction.objectStore("Utilisateur");
var request = objectStore.get(1);
request.onerror = function(event) {
    console.log("Echec de la requête !");
}
request.onsuccess = function(event) {
    if (request.result) {
        console.log(request.result.Nickname);
        console.log(request.result.eMail);
    } else {
        console.log("Aucune entrée disponible");
    }
};

Ce code vous permet de re­cher­cher l’entrée sous la clé 1, c’est-à-dire avec la valeur id 1. Si la tran­sac­tion est un échec, un message d’erreur est généré. En revanche, lorsque la tran­sac­tion est un succès, vous découvrez le contenu des deux entrées Nickname et Email. Si aucune entrée ne peut être trouvée sous le numéro, vous en êtes également informé.

Un curseur vous aide lorsque vous re­cher­chez plusieurs entrées en même temps. Cette fonction appelle une entrée après l’autre. Dans ce cadre, vous pouvez prendre en con­si­dé­ra­tion toutes les entrées de la base de données ou sé­lec­tion­ner uni­que­ment un domaine clé déterminé.

var objectStore = db.transaction("Utilisateur").objectStore("Utilisateur");
objectStore.openCursor().onsuccess = function(event) {
    var cursor = event.target.result;
    if (cursor) {
        console.log(cursor.key);
        console.log(cursor.value.Nickname);
        console.log(cursor.value.eMail);
        cursor.continue();
    } else {
        console.log("Plus d’entrées disponibles !");
    }
};

Nous avons généré deux index au préalable pour pouvoir consulter ces in­for­ma­tions. Cette con­sul­ta­tion s’effectue aussi via get.

var index = objectStore.index("Nickname");
index.get("Raptor123").onsuccess = function(event) {
    console.log(event.target.result.eMail);
};

Enfin, si vous souhaitez supprimer une entrée d’une base de données, procédez de la même façon que pour ajouter un ensemble de données avec une tran­sac­tion readwrite.

var request = db.transaction(["Utilisateur"], "readwrite")
    .objectStore("Utilisateur")
    .delete(1);
request.onsuccess = function(event) {
    console.log("Entrée surpprimée avec succès !");
};
En résumé

Cet article vous a ac­com­pagné dans vos premiers pas avec IndexedDB. Vous trouverez de plus amples in­for­ma­tions auprès de Mozilla ou Google. Toutefois, Google utilise une bi­blio­thèque spéciale dans le code type, ce qui explique pourquoi le code peut différer en partie de celui de Mozilla.

Aller au menu principal