
IndexedDB è una delle soluzioni di archiviazione introdotte nei browser nel corso degli anni.
È un database chiave/valore (un database NoSQL) considerato la soluzione definitiva per archiviare dati nel browser.
È un’API asincrona: l’esecuzione del codice non blocca il resto del programma, con un vantaggio notevole per l’esperienza utente. Può archiviare una quantità di dati non definita a priori, anche se oltre una certa soglia viene chiesto all’utente di autorizzare il sito a occupare più spazio.
Esistono altri metodi: i cookie e il Web Storage ( localStorage e sessionStorage ). Lo storage local/session ha lo svantaggio di essere limitato a poco spazio, dato che i browser offrono da 2MB a 10MB per sito.
Anche se tecnicamente puoi creare più database per sito, di solito ne crei uno solo e al suo interno crei più object store.
Un database è privato per dominio: nessun altro sito può accedere a un database IndexedDB che non è il suo.
Ogni object store contiene di norma un insieme di elementi, che possono essere:
- objects
- arrays
- strings
- numbers
- dates
Per esempio puoi avere uno store che contiene i post e un altro che contiene i commenti.
Uno store contiene un certo numero di oggetti dotati di una chiave univoca, che rappresenta il modo in cui un oggetto viene identificato.
Puoi modificare questi oggetti tramite transazioni, eseguendo operazioni di inserimento, modifica, eliminazione e iterazione sugli elementi che contengono.
Dall’arrivo delle Promise in ES6, e dal successivo passaggio delle API alle promesse, l’API IndexedDB sembra un po’ vecchia scuola.
Non c’è niente di male, ma in tutti gli esempi che seguono userò la libreria promise IndexedDB di Jake Archibald, un sottile strato sopra l’API IndexedDB che la rende più comoda da usare.
La stessa libreria è usata in tutti gli esempi su IndexedDB del sito Google Developers
Creare un database IndexedDB
Il modo più semplice è usare unpkg, aggiungendolo nell’head della pagina:
<script type="module">
import { openDB, deleteDB } from 'https://unpkg.com/idb?module'
</script>Prima di usare l’API IndexedDB verifica sempre che il browser la supporti: anche se è ormai ampiamente disponibile, non sai mai quale browser stia usando l’utente.
(async () => {
'use strict'
if (!('indexedDB' in window)) {
console.warn('IndexedDB not supported')
return
}
})()Come creare un database IndexedDB
Con openDB():
(async () => {
//...
const dbName="mydbname"
const storeName="store1"
const version = 1
const db = await openDB(dbName, version, {
upgrade(db, oldVersion, newVersion, transaction) {
const store = db.createObjectStore(storeName)
}
})
})()I primi 2 parametri sono il nome del database e la versione. Il terzo, facoltativo, è un oggetto che contiene una funzione chiamata solo se il numero di versione è superiore a quello del database già installato. Nel corpo di quella funzione puoi aggiornare la struttura del database (store e indici).
Aggiungere dati in uno store
Aggiungere dati alla creazione dello store: inizializzare lo store
Per aggiungere dati si usa il metodo put dell’object store, ma prima dobbiamo inizializzarlo con db.createObjectStore() al momento della creazione.
Con put il valore è il primo argomento e la chiave il secondo. Il motivo è che, se specifichi keyPath quando crei l’object store, non devi indicare il nome della chiave a ogni put(): basta passare il valore.
Questo popola store0 appena lo creiamo:
(async () => {
//...
const dbName="mydbname"
const storeName="store0"
const version = 1
const db = await openDB(dbName, version,{
upgrade(db, oldVersion, newVersion, transaction) {
const store = db.createObjectStore(storeName)
store.put('Hello world!', 'Hello')
}
})
})()Aggiungere dati a store già creato, con le transazioni
Per aggiungere elementi in un secondo momento devi creare una transazione in lettura/scrittura, che garantisce l’integrità del database: se un’operazione fallisce, tutte le operazioni della transazione vengono annullate e lo stato torna a quello iniziale.
Usa quindi un riferimento all’oggetto dbPromise ottenuto chiamando openDB, ed esegui:
(async () => {
//...
const dbName="mydbname"
const storeName="store0"
const version = 1
const db = await openDB(/* ... */)
const tx = db.transaction(storeName, 'readwrite')
const store = await tx.objectStore(storeName)
const val="hey!"
const key = 'Hello again'
const value = await store.put(val, key)
await tx.done
})()Leggere dati da uno store
Leggere un elemento da un object store: get()
const key = 'Hello again'
const item = await db.transaction(storeName).objectStore(storeName).get(key)Leggere tutti gli elementi di un object store: getAll()
Ottenere tutte le chiavi archiviate
const items = await db.transaction(storeName).objectStore(storeName).getAllKeys()Ottenere tutti i valori archiviati
const items = await db.transaction(storeName).objectStore(storeName).getAll()Eliminare dati da IndexedDB
Eliminare il database, un object store e i dati
Eliminare del tutto un database IndexedDB
const dbName="mydbname"
await deleteDB(dbName)Per eliminare dati in un object store
Usiamo una transazione:
(async () => {
//...
const dbName="mydbname"
const storeName="store0"
const version = 1
const db = await openDB(dbName, version, {
upgrade(db, oldVersion, newVersion, transaction) {
const store = db.createObjectStore(storeName)
}
})
const tx = await db.transaction(storeName, 'readwrite')
const store = await tx.objectStore(storeName)
const key = 'Hello again'
await store.delete(key)
await tx.done
})()Migrare da una versione precedente del database
Il terzo parametro (facoltativo) della funzione openDB() è un oggetto che può contenere una funzione di aggiornamento, chiamata solo se il numero di versione è superiore a quello del database installato. Nel corpo di quella funzione puoi aggiornare la struttura del database (store e indici):
const name="mydbname"
const version = 1
openDB(name, version, {
upgrade(db, oldVersion, newVersion, transaction) {
console.log(oldVersion)
}
})In questa callback puoi controllare da quale versione sta aggiornando l’utente ed eseguire operazioni diverse di conseguenza.
Puoi migrare da una versione precedente del database con questa sintassi
(async () => {
//...
const dbName="mydbname"
const storeName="store0"
const version = 1
const db = await openDB(dbName, version, {
upgrade(db, oldVersion, newVersion, transaction) {
switch (oldVersion) {
case 0:
// uno store introdotto nella versione 1
db.createObjectStore('store1')
case 1:
// elimina il vecchio store nella versione 2, creane uno nuovo
db.createObjectStore('store2', { keyPath: 'name' })
}
db.createObjectStore(storeName)
}
})
})()Chiave univoca
createObjectStore()come vedi nel caso 1 accetta un secondo parametro che indica la chiave di indice del database. È molto utile quando archivi oggetti: le chiamate put() non hanno bisogno di un secondo parametro, ma possono prendere solo il valore (un oggetto) e la chiave verrà mappata sulla proprietà dell’oggetto che porta quel nome.
L’indice ti dà un modo per recuperare più tardi un valore tramite quella chiave specifica, e deve essere univoco (ogni elemento deve avere una chiave diversa).
Una chiave può essere impostata con incremento automatico, così non devi gestirla nel codice client:
db.createObjectStore('notes', { autoIncrement: true })Usa l’incremento automatico se i tuoi valori non contengono già una chiave univoca.
Verificare l’esistenza di un object store
Puoi verificare se un object store esiste già chiamando il metodo objectStoreNames():
const storeName="store1"
if (!db.objectStoreNames.contains(storeName)) {
db.createObjectStore(storeName)
}Eliminare da IndexedDB
Eliminare il database, un object store e i dati
Eliminare un database
await deleteDB('mydb')Eliminare un object store
Un object store può essere eliminato solo nella callback di apertura di un db, e quella callback viene chiamata solo se specifichi una versione superiore a quella installata:
const db = await openDB('dogsdb', 2, {
upgrade(db, oldVersion, newVersion, transaction) {
switch (oldVersion) {
case 0:
// uno store introdotto nella versione 1
db.createObjectStore('store1')
case 1:
// elimina il vecchio store nella versione 2, creane uno nuovo
db.deleteObjectStore('store1')
db.createObjectStore('store2')
}
}
})Per eliminare dati in un object store, usa una transazione
const key = 232
const db = await openDB(/*...*/)
const tx = await db.transaction('store', 'readwrite')
const store = await tx.objectStore('store')
await store.delete(key)
await tx.complete

