// apis · Web Platform Advent #23
IndexedDB, qué es: la base de datos del navegador explicada
IndexedDB es la base de datos de verdad del navegador: asíncrona, transaccional y mucho mayor que localStorage. Cómo funciona onupgradeneeded, por qué se te cierran las transacciones, para qué sirven los índices y cuándo usarla en lugar de Web Storage.
IndexedDB es la base de datos de verdad del navegador. Donde el Web Storage te da una caja síncrona de cadenas limitada a unos 5 MB, IndexedDB ofrece un almacén asíncrono y transaccional para datos estructurados, dimensionado en cientos de megabytes o más. No es SQL. Es un almacén clave-valor con índices, y en cuanto eso encaja el resto de la API se sigue solo.
Abrir una base y el único sitio donde puedes cambiar su forma
Se abre una base por su nombre y un número de versión. Si la versión que pides es mayor que la que hay en disco, el navegador dispara upgradeneeded, y ese manejador es el único lugar donde puedes crear o borrar almacenes de objetos e índices.
const request = indexedDB.open('notes-db', 1);
request.onupgradeneeded = (event) => {
const db = event.target.result;
const store = db.createObjectStore('notes', { keyPath: 'id', autoIncrement: true });
store.createIndex('by_tag', 'tag', { unique: false });
};
request.onsuccess = (event) => {
const db = event.target.result;
};
request.onerror = () => console.error(request.error); Esto pilla desprevenido porque invierte la costumbre. No puedes crear un almacén de forma perezosa la primera vez que lo necesitas. El esquema vive en una sola función, atada a un número de versión que subes cuando la estructura cambia.
Todo es una petición, y las peticiones son asíncronas
Casi cada llamada de IndexedDB devuelve un IDBRequest en vez de un valor. El resultado se lee en onsuccess y el fallo se trata en onerror. Nada bloquea el hilo principal, que es toda la razón para preferirla a localStorage en cuanto el volumen importa.
Lecturas y escrituras ocurren dentro de una transacción
No se toca un almacén directamente. Se abre una transacción sobre uno o varios almacenes, en modo readonly o readwrite, y se trabaja a través de ella.
const tx = db.transaction('notes', 'readwrite');
const store = tx.objectStore('notes');
store.add({ title: 'Buy milk', tag: 'errand', done: false });
tx.oncomplete = () => console.log('written');
tx.onerror = () => console.error(tx.error); Aquí está la trampa que conviene conocer de antemano. Una transacción confirma automáticamente en cuanto no le queda trabajo pendiente. Si esperas algo ajeno en medio, por ejemplo un fetch, la transacción se cierra mientras esperas y la siguiente llamada lanza TransactionInactiveError. Haz primero el trabajo de fuera y abre después la transacción.
Los índices, que son el motivo del nombre
Por defecto los registros se recuperan por su clave primaria. Un índice es una segunda ordenación de los mismos registros que permite consultar por otra propiedad.
const tx = db.transaction('notes', 'readonly');
const index = tx.objectStore('notes').index('by_tag');
const req = index.getAll('errand');
req.onsuccess = () => console.log(req.result); Para rangos en vez de coincidencias exactas, combina un índice con IDBKeyRange y un cursor, que recorre los resultados de uno en uno en lugar de cargarlos todos en memoria.
Qué puede guardar y cuánto
IndexedDB acepta todo lo que maneja el algoritmo de clonado estructurado: objetos, arrays, Date, Blob, File, ArrayBuffer. Las funciones y los nodos del DOM no son clonables y lanzan error. No hace falta pasar por JSON.stringify, lo que además significa que los números siguen siendo números y las fechas, fechas.
La cuota es una parte del disco disponible y no un tope fijo de 5 MB, así que es el sitio adecuado para respuestas de API en caché, documentos sin conexión o archivos multimedia. Ese almacenamiento es de mejor esfuerzo por defecto y puede desalojarse bajo presión de disco; llama a navigator.storage.persist() si los datos deben sobrevivir a eso, y a navigator.storage.estimate() para ver cuánto consumes.
¿IndexedDB o localStorage?
| localStorage | IndexedDB | |
|---|---|---|
| Estilo de API | Síncrona, bloquea el hilo principal | Asíncrona, basada en eventos |
| Valores | Solo cadenas | Valores clonables estructurados |
| Tamaño orientativo | Unos 5 MB por origen | Una parte del disco libre |
| Consultas | Solo por clave | Por clave, por índice, por rango con cursores |
| Bueno para | Tema, banderas, preferencias pequeñas | Datos sin conexión, cachés, archivos, listas grandes |
Ambos están limitados al origen y ninguno es sitio para secretos: cualquier script de la página puede leerlos.
¿Conviene usar una biblioteca envoltorio?
La API en crudo es verbosa porque es anterior a las promesas. Pequeñas bibliotecas envuelven las peticiones en promesas y eliminan casi todo el código repetitivo; son una opción por defecto razonable para código de aplicación. Aun así, aprende antes el modelo de debajo: upgradeneeded, las transacciones y su confirmación automática se comportan igual por dentro, y todos los fallos desconcertantes que te encontrarás vienen de esos tres puntos, no del envoltorio.
Elige IndexedDB cuando los datos sean estructurados, grandes o deban estar disponibles sin conexión, a menudo junto a un service worker. Deja localStorage para el puñado de banderas pequeñas donde una lectura síncrona sea de verdad más simple.