
IndexedDB es una de las soluciones de almacenamiento que los navegadores han ido incorporando con los años.
Es una base de datos clave/valor (una base de datos noSQL) considerada la solución definitiva para guardar datos en el navegador.
Es una API asíncrona, lo que significa que su ejecución no bloquea el resto del programa, con la mejora de experiencia que eso supone para el usuario. Puede guardar una cantidad indefinida de datos, aunque a partir de cierto umbral se pide al usuario que autorice al sitio a almacenar más.
Existen otros métodos, las cookies y el Web Storage ( localStorage y sessionStorage ). El almacenamiento local o de sesión tiene el inconveniente de estar limitado a un tamaño pequeño, ya que los navegadores ofrecen de 2MB a 10MB de espacio por sitio.
Aunque técnicamente puedes crear varias bases de datos por sitio, lo normal es crear una sola y, dentro de ella, varios almacenes de objetos.
Una base de datos es privada de un dominio, de modo que ningún otro sitio puede acceder a una base IndexedDB ajena.
Cada almacén de objetos suele contener un conjunto de cosas, que pueden ser:
- objects
- arrays
- strings
- numbers
- dates
Por ejemplo, puedes tener un almacén con los mensajes y otro con los comentarios.
Un almacén contiene una serie de objetos que tienen una clave única, que representa la forma de identificar cada objeto.
Puedes modificar esos objetos mediante transacciones, con operaciones de añadido, modificación, borrado e iteración sobre los elementos que contienen.
Desde la llegada de las Promises en ES6, y el paso posterior de las API a las promesas, la API de IndexedDB parece un poco anticuada.
No hay nada malo en ello, pero en todos los ejemplos que voy a explicar usaré la librería basada en promesas IndexedDB de Jake Archibald, una capa fina sobre la API de IndexedDB que la hace mucho más cómoda de usar.
Esta librería se usa también en todos los ejemplos sobre IndexedDB del sitio Google Developers
Crear una base de datos IndexedDB
La forma más sencilla es usar unpkg, añadiéndolo a la cabecera de la página:
<script type="module">
import { openDB, deleteDB } from 'https://unpkg.com/idb?module'
</script>Antes de usar la API de IndexedDB, comprueba siempre que el navegador la soporta: aunque está ampliamente disponible, nunca sabes con qué navegador llega el usuario.
(async () => {
'use strict'
if (!('indexedDB' in window)) {
console.warn('IndexedDB not supported')
return
}
})()Cómo crear una base de datos IndexedDB
Usa 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)
}
})
})()Los 2 primeros parámetros son el nombre de la base de datos y la versión. El tercero, opcional, es un objeto que contiene una función que solo se llama si el número de versión es superior al de la base de datos instalada. En el cuerpo de esa función puedes actualizar la estructura (almacenes e índices) de la base de datos.
Añadir datos a un almacén
Añadir datos al crear el almacén: la inicialización
Para añadir datos hay que usar el método put del almacén de objetos, pero antes tenemos que inicializarlo con db.createObjectStore() en el momento de crearlo.
Al usar put, el valor es el primer argumento y la clave el segundo. Esto es así porque, si indicas keyPath al crear el almacén de objetos, no necesitas escribir el nombre de la clave en cada llamada a put(): basta con pasar el valor.
Esto rellena store0 en cuanto lo hemos creado:
(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')
}
})
})()Añadir datos a un almacén ya creado, mediante transacciones
Para añadir elementos más adelante tienes que crear una transacción de lectura/escritura que garantice la integridad de la base de datos (si una operación falla, todas las operaciones de la transacción se anulan y el estado vuelve al inicial).
Para ello, usa una referencia al objeto dbPromise que hemos obtenido al llamar a openDB, y lanza:
(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
})()Obtener datos de un almacén
Obtener un elemento de un almacén de objetos: get()
const key = 'Hello again'
const item = await db.transaction(storeName).objectStore(storeName).get(key)Obtener todos los elementos de un almacén de objetos: getAll()
Obtener todas las claves guardadas
const items = await db.transaction(storeName).objectStore(storeName).getAllKeys()Obtener todos los valores guardados
const items = await db.transaction(storeName).objectStore(storeName).getAll()Eliminar datos de IndexedDB
Borrado de la base de datos, de un almacén de objetos y de los datos
Eliminar por completo una base de datos IndexedDB
const dbName="mydbname"
await deleteDB(dbName)Para eliminar datos de un almacén de objetos
Usamos una transacción:
(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
})()Migrar desde una versión anterior de la base de datos
El tercer parámetro (opcional) de la función openDB() es un objeto que puede contener una función de actualización que solo se llama si el número de versión es superior al de la base de datos instalada. En el cuerpo de esa función puedes actualizar la estructura (almacenes e índices) de la base de datos:
const name="mydbname"
const version = 1
openDB(name, version, {
upgrade(db, oldVersion, newVersion, transaction) {
console.log(oldVersion)
}
})En ese callback puedes comprobar desde qué versión actualiza el usuario y ejecutar unas operaciones u otras en consecuencia.
Puedes migrar desde una versión anterior de la base de datos con esta sintaxis
(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:
// un almacén introducido en la versión 1
db.createObjectStore('store1')
case 1:
// eliminar el almacén antiguo en la versión 2 y crear uno nuevo
db.createObjectStore('store2', { keyPath: 'name' })
}
db.createObjectStore(storeName)
}
})
})()Clave única
createObjectStore(), como ves en el caso 1, acepta un segundo parámetro que indica la clave de índice de la base de datos. Resulta muy útil cuando guardas objetos: las llamadas a put() no necesitan un segundo parámetro, basta con pasar el valor (un objeto) y la clave se asociará a la propiedad del objeto que lleve ese nombre.
El índice te da una forma de recuperar más adelante un valor por esa clave concreta, y debe ser único (cada elemento tiene que tener una clave distinta).
Una clave se puede configurar con autoincremento, así no tienes que gestionarla en el código cliente:
db.createObjectStore('notes', { autoIncrement: true })Usa el autoincremento si tus valores no contienen ya una clave única.
Comprobar si existe un almacén de objetos
Puedes comprobar si un almacén de objetos ya existe llamando al método objectStoreNames():
const storeName="store1"
if (!db.objectStoreNames.contains(storeName)) {
db.createObjectStore(storeName)
}Eliminar de IndexedDB
Borrado de la base de datos, de un almacén de objetos y de los datos
Eliminar una base de datos
await deleteDB('mydb')Eliminar un almacén de objetos
Un almacén de objetos solo se puede eliminar en el callback de apertura de una base de datos, y ese callback solo se llama si indicas una versión superior a la que ya está instalada:
const db = await openDB('dogsdb', 2, {
upgrade(db, oldVersion, newVersion, transaction) {
switch (oldVersion) {
case 0:
// un almacén introducido en la versión 1
db.createObjectStore('store1')
case 1:
// eliminar el almacén antiguo en la versión 2 y crear uno nuevo
db.deleteObjectStore('store1')
db.createObjectStore('store2')
}
}
})Para eliminar datos de un almacén de objetos, usa una transacción
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

