Crear aplicaciones que funcionen sin problemas sin conexión se ha convertido en un requisito clave en el desarrollo web moderno. IndexedDB ofrece una solución potente para los desarrolladores que buscan almacenar y gestionar grandes cantidades de datos directamente en el navegador. Esta base de datos NoSQL del lado del cliente permite una gestión eficiente de los datos, lo que la hace ideal para aplicaciones web que necesitan funcionalidad sin conexión. En este artículo, exploraremos los conceptos básicos de IndexedDB, sus características clave y te guiaremos a través de los pasos para configurarla y usarla en tus proyectos web.
¿Qué es IndexedDB?
IndexedDB es una potente base de datos NoSQL del lado del cliente que permite a las aplicaciones web almacenar y gestionar datos estructurados directamente en el navegador. Proporciona una solución eficiente para gestionar grandes conjuntos de datos localmente, lo que la hace ideal para la funcionalidad sin conexión y las aplicaciones con gran cantidad de datos.

Una de las principales ventajas de IndexedDB es su capacidad para admitir aplicaciones web sin conexión. Permite a los desarrolladores almacenar cantidades sustanciales de datos, utilizar la indexación para una recuperación rápida, gestionar transacciones y navegar por los datos mediante cursores. Estas características la convierten en una excelente opción para gestionar datos complejos en aplicaciones web.
La mayoría de los navegadores web modernos, incluidos Chrome, Firefox, Safari y Edge, son compatibles con IndexedDB. Sin embargo, es importante asegurarse de que la versión de IndexedDB sea compatible con el navegador al que te diriges para tu aplicación web.
Para empezar a usar IndexedDB, el primer paso es abrir una conexión a la base de datos, que es necesaria para crearla e interactuar con ella.

Primeros pasos con IndexedDB: Una guía completa
IndexedDB es una potente solución de almacenamiento del lado del cliente para aplicaciones web. Esta guía te guiará a través de todo, desde la configuración básica hasta las técnicas avanzadas y la implementación en el mundo real.
Configuración de IndexedDB
Creación y apertura de una base de datos
Para crear una nueva base de datos en IndexedDB, utilizarás el método indexedDB.open()
:
const request = indexedDB.open("MyDatabase", 1);
request.onupgradeneeded = (event) => {
const db = event.target.result;
// Create object stores and indexes here
};
El método toma dos parámetros: el nombre de la base de datos y el número de versión. Para abrir una base de datos existente, simplemente puedes llamar al método sin especificar un número de versión.
Control de versiones de la base de datos
IndexedDB admite el control de versiones para gestionar los cambios de esquema. Cuando abres una base de datos con un número de versión superior al existente, se activa el evento onupgradeneeded
, lo que te permite actualizar el esquema de tu base de datos.
Creación de almacenes de objetos e índices
Los almacenes de objetos son contenedores para tus datos en IndexedDB:
request.onupgradeneeded = (event) => {
const db = event.target.result;
// Create an object store with a key path
const objectStore = db.createObjectStore("customers", { keyPath: "id" });
// Create an index for efficient querying
objectStore.createIndex("name", "name", { unique: false });
};
Trabajar con transacciones
Comprensión de los conceptos básicos de las transacciones
Las transacciones en IndexedDB mantienen la integridad de los datos agrupando varias operaciones en una sola unidad atómica. Se aseguran de que todos los cambios se apliquen o de que ninguno lo haga.
Creación y gestión de transacciones
const transaction = db.transaction(["customers"], "readwrite");
const objectStore = transaction.objectStore("customers");
transaction.oncomplete = (event) => {
console.log("Transaction completed successfully");
};
transaction.onerror = (event) => {
console.error("Transaction failed");
};
Manejo de errores y reversión
Si una operación falla, puedes usar el método abort()
para revertir toda la transacción:
try {
// Perform operations
} catch (error) {
transaction.abort();
console.error("Transaction aborted:", error);
}
Operaciones de datos en IndexedDB
Añadir datos
const customerData = { id: "00001", name: "John Doe", email: "john@example.com" };
const request = objectStore.add(customerData);
request.onsuccess = (event) => {
console.log("Data added successfully");
};
Recuperación de datos
Recuperación básica de datos por clave:
const request = objectStore.get("00001");
request.onsuccess = (event) => {
console.log("Customer data:", request.result);
};
Filtrado con índices
const index = objectStore.index("name");
const request = index.get("John Doe");
request.onsuccess = (event) => {
console.log("Found by name:", request.result);
};
Métodos de consulta avanzados
Para consultas complejas, IndexedDB ofrece consultas de rango y consultas compuestas utilizando métodos como openCursor()
e IDBKeyRange
:
const range = IDBKeyRange.bound("A", "F"); // Names starting with A through F
const request = index.openCursor(range);
Actualización de registros
const updateData = { id: "00001", name: "John Smith", email: "john@example.com" };
const request = objectStore.put(updateData);
Eliminación de registros
const request = objectStore.delete("00001");
request.onsuccess = (event) => {
console.log("Record deleted");
};
Trabajar con cursores
Comprensión de la funcionalidad del cursor
Los cursores te permiten iterar eficientemente sobre los registros en un almacén de objetos o índice, proporcionando una forma de recorrer y manipular los datos.
Navegación a través de los registros
const request = objectStore.openCursor();
request.onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
console.log("Key:", cursor.key, "Value:", cursor.value);
cursor.continue(); // Move to the next record
} else {
console.log("No more records");
}
};
Modificación de datos mediante cursores
const transaction = db.transaction(["customers"], "readwrite");
const objectStore = transaction.objectStore("customers");
const request = objectStore.openCursor();
request.onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
if (cursor.value.status === "inactive") {
const updateData = cursor.value;
updateData.status = "active";
cursor.update(updateData);
}
cursor.continue();
}
};
Gestión y actualizaciones del esquema
Actualización del esquema de la base de datos
Cuando tu aplicación evoluciona, es posible que necesites modificar el esquema de tu base de datos:
const request = indexedDB.open("MyDatabase", 2); // Increase version number
request.onupgradeneeded = (event) => {
const db = event.target.result;
// Check if the object store exists
if (!db.objectStoreNames.contains("newStore")) {
db.createObjectStore("newStore", { keyPath: "id" });
}
};
Migración de datos durante las actualizaciones
request.onupgradeneeded = (event) => {
const db = event.target.result;
const oldVersion = event.oldVersion;
if (oldVersion < 1) {
// First version setup
}
if (oldVersion < 2) {
// Migrate data to new schema
const transaction = event.target.transaction;
const oldStore = transaction.objectStore("oldStore");
const newStore = db.createObjectStore("newStore", { keyPath: "id" });
oldStore.openCursor().onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
newStore.add(cursor.value);
cursor.continue();
}
};
}
};
Optimización del rendimiento
Operaciones masivas eficientes
Para un mejor rendimiento, utiliza operaciones masivas cuando trabajes con varios registros:
const transaction = db.transaction(["customers"], "readwrite");
const store = transaction.objectStore("customers");
// Add multiple records in a single transaction
customerList.forEach(customer => {
store.add(customer);
});
Aprovechamiento de los índices para consultas más rápidas
Crea índices en las propiedades consultadas con frecuencia para garantizar una recuperación de datos más rápida:
objectStore.createIndex("email", "email", { unique: true });
objectStore.createIndex("lastLogin", "lastLogin", { unique: false });
Prácticas recomendadas para la gestión de conexiones
Abre las conexiones solo cuando sea necesario y ciérralas cuando hayas terminado:
let db;
function openDB() {
const request = indexedDB.open("MyDatabase", 1);
request.onsuccess = (event) => {
db = event.target.result;
};
return request;
}
// When you're done with the database
function closeDB() {
if (db) {
db.close();
db = null;
}
}
Ejemplo del mundo real: Gestor de tareas con soporte sin conexión
Configuración de la base de datos
const request = indexedDB.open("TaskManagerDB", 1);
request.onupgradeneeded = (event) => {
const db = event.target.result;
const taskStore = db.createObjectStore("tasks", { keyPath: "id", autoIncrement: true });
// Create indexes for querying
taskStore.createIndex("status", "status", { unique: false });
taskStore.createIndex("dueDate", "dueDate", { unique: false });
};
Añadir tareas
function addTask(taskData) {
const transaction = db.transaction(["tasks"], "readwrite");
const taskStore = transaction.objectStore("tasks");
return new Promise((resolve, reject) => {
const request = taskStore.add({
title: taskData.title,
description: taskData.description,
status: "pending",
dueDate: taskData.dueDate,
createdAt: new Date()
});
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
Recuperación y visualización de tareas
function getAllTasks() {
const transaction = db.transaction(["tasks"], "readonly");
const taskStore = transaction.objectStore("tasks");
return new Promise((resolve, reject) => {
const request = taskStore.openCursor();
const tasks = [];
request.onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
tasks.push(cursor.value);
cursor.continue();
} else {
resolve(tasks);
}
};
request.onerror = () => reject(request.error);
});
}
Actualización y eliminación de tareas
function updateTaskStatus(id, newStatus) {
const transaction = db.transaction(["tasks"], "readwrite");
const taskStore = transaction.objectStore("tasks");
return new Promise((resolve, reject) => {
const getRequest = taskStore.get(id);
getRequest.onsuccess = () => {
const task = getRequest.result;
task.status = newStatus;
task.updatedAt = new Date();
const updateRequest = taskStore.put(task);
updateRequest.onsuccess = () => resolve(true);
updateRequest.onerror = () => reject(updateRequest.error);
};
getRequest.onerror = () => reject(getRequest.error);
});
}
Sincronización con el servidor
function syncWithServer() {
if (!navigator.onLine) {
return Promise.reject(new Error("No internet connection"));
}
return getAllTasks()
.then(tasks => {
// Filter tasks that need syncing
const unsynced = tasks.filter(task => !task.synced);
// Send to server using fetch API
return fetch('https://api.example.com/tasks/sync', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(unsynced)
});
})
.then(response => response.json())
.then(result => {
// Mark tasks as synced
const transaction = db.transaction(["tasks"], "readwrite");
const taskStore = transaction.objectStore("tasks");
result.syncedIds.forEach(id => {
const request = taskStore.get(id);
request.onsuccess = () => {
const task = request.result;
task.synced = true;
taskStore.put(task);
};
});
return result;
});
}
// Listen for online events to sync automatically
window.addEventListener('online', syncWithServer);
Integración de Apidog para la gestión de API con IndexedDB
Al crear aplicaciones con IndexedDB para el almacenamiento del lado del cliente, a menudo necesitarás interactuar con las API de back-end para la sincronización de datos. Apidog proporciona una solución perfecta para gestionar estas interacciones de la API.

Por qué Apidog mejora el desarrollo de IndexedDB
A medida que desarrollas aplicaciones con capacidad sin conexión con IndexedDB, Apidog ofrece varias ventajas:
Sincronización en tiempo real: Apidog garantiza que tus puntos finales de la API utilizados para la sincronización de datos estén siempre configurados y probados correctamente, eliminando los problemas de integración cuando tu aplicación se conecta.
Respuestas de API simuladas: Al desarrollar la funcionalidad sin conexión, los motores de simulación inteligentes de Apidog te permiten simular las respuestas de la API para probar tu lógica de sincronización de IndexedDB sin un servidor en vivo.

Diseño colaborativo de API: Cuando tu equipo está trabajando simultáneamente en el almacenamiento front-end y las API de back-end, Apidog facilita la colaboración en tiempo real en las especificaciones de la API.

Al integrar Apidog en tu flujo de trabajo de desarrollo, creas un puente perfecto entre el almacenamiento del lado del cliente y el procesamiento del lado del servidor, lo que hace que tu aplicación sea más robusta y fácil de mantener.