Construir aplicações que funcionem perfeitamente offline se tornou uma exigência fundamental no desenvolvimento web moderno. O IndexedDB oferece uma solução poderosa para desenvolvedores que buscam armazenar e gerenciar grandes quantidades de dados diretamente no navegador. Este banco de dados NoSQL do lado do cliente permite um manuseio de dados eficiente, tornando-o ideal para aplicações web que precisam de funcionalidade offline. Neste artigo, exploraremos os fundamentos do IndexedDB, suas principais características e guiaremos você pelos passos para configurá-lo e usá-lo em seus projetos web.
O que é o IndexedDB?
IndexedDB é um poderoso banco de dados NoSQL do lado do cliente que permite que aplicações web armazenem e gerenciem dados estruturados diretamente no navegador. Ele fornece uma solução eficiente para gerenciar grandes conjuntos de dados localmente, tornando-o ideal para funcionalidade offline e aplicações intensivas em dados.

Uma das principais vantagens do IndexedDB é sua capacidade de suportar aplicações web offline. Ele permite que os desenvolvedores armazenem quantidades substanciais de dados, usem indexação para recuperação rápida, gerenciem transações e naveguem pelos dados usando cursores. Essas características o tornam uma excelente escolha para lidar com dados complexos em aplicativos web.
A maioria dos navegadores web modernos, incluindo Chrome, Firefox, Safari e Edge, suporta o IndexedDB. No entanto, é importante garantir que a versão do IndexedDB seja compatível com o navegador que você está direcionando para sua aplicação web.
Para começar a usar o IndexedDB, o primeiro passo é abrir uma conexão com o banco de dados, o que é necessário para criar e interagir com ele.

Introdução ao IndexedDB: Um Guia Abrangente
O IndexedDB é uma poderosa solução de armazenamento do lado do cliente para aplicações web. Este guia irá guiá-lo por tudo, desde a configuração básica até técnicas avançadas e implementação no mundo real.
Configurando o IndexedDB
Criando e Abrindo um Banco de Dados
Para criar um novo banco de dados no IndexedDB, você usará o método indexedDB.open()
:
const request = indexedDB.open("MeuBancoDeDados", 1);
request.onupgradeneeded = (event) => {
const db = event.target.result;
// Crie lojas de objetos e índices aqui
};
O método aceita dois parâmetros: o nome do banco de dados e o número da versão. Para abrir um banco de dados existente, você pode simplesmente chamar o método sem especificar um número de versão.
Versionamento de Banco de Dados
O IndexedDB suporta versionamento para lidar com mudanças de esquema. Quando você abre um banco de dados com um número de versão mais alto do que o existente, o evento onupgradeneeded
é acionado, permitindo que você atualize o esquema do seu banco de dados.
Criando Lojas de Objetos e Índices
Lojas de objetos são contêineres para seus dados no IndexedDB:
request.onupgradeneeded = (event) => {
const db = event.target.result;
// Crie uma loja de objetos com um caminho de chave
const objectStore = db.createObjectStore("clientes", { keyPath: "id" });
// Crie um índice para consultas eficientes
objectStore.createIndex("nome", "nome", { unique: false });
};
Trabalhando com Transações
Entendendo os Fundamentos das Transações
Transações no IndexedDB mantêm a integridade dos dados agrupando várias operações em uma única unidade atômica. Elas garantem que todas as alterações sejam aplicadas ou nenhuma.
Criando e Gerenciando Transações
const transaction = db.transaction(["clientes"], "readwrite");
const objectStore = transaction.objectStore("clientes");
transaction.oncomplete = (event) => {
console.log("Transação concluída com sucesso");
};
transaction.onerror = (event) => {
console.error("Transação falhou");
};
Tratamento de Erros e Reversão
Se uma operação falhar, você pode usar o método abort()
para reverter toda a transação:
try {
// Realizar operações
} catch (error) {
transaction.abort();
console.error("Transação abortada:", error);
}
Operações de Dados no IndexedDB
Adicionando Dados
const customerData = { id: "00001", nome: "João Silva", email: "joao@exemplo.com" };
const request = objectStore.add(customerData);
request.onsuccess = (event) => {
console.log("Dados adicionados com sucesso");
};
Recuperando Dados
Recuperação básica de dados por chave:
const request = objectStore.get("00001");
request.onsuccess = (event) => {
console.log("Dados do cliente:", request.result);
};
Filtrando com Índices
const index = objectStore.index("nome");
const request = index.get("João Silva");
request.onsuccess = (event) => {
console.log("Encontrado pelo nome:", request.result);
};
Métodos de Consulta Avançados
Para consultas complexas, o IndexedDB oferece consultas de intervalo e consultas compostas usando métodos como openCursor()
e IDBKeyRange
:
const range = IDBKeyRange.bound("A", "F"); // Nomes começando com A até F
const request = index.openCursor(range);
Atualizando Registros
const updateData = { id: "00001", nome: "João Pereira", email: "joao@exemplo.com" };
const request = objectStore.put(updateData);
Excluindo Registros
const request = objectStore.delete("00001");
request.onsuccess = (event) => {
console.log("Registro excluído");
};
Trabalhando com Cursores
Entendendo a Funcionalidade do Cursor
Os cursores permitem iterar eficientemente sobre registros em uma loja de objetos ou índice, fornecendo uma maneira de percorrer e manipular dados.
Navegando pelos Registros
const request = objectStore.openCursor();
request.onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
console.log("Chave:", cursor.key, "Valor:", cursor.value);
cursor.continue(); // Move para o próximo registro
} else {
console.log("Sem mais registros");
}
};
Modificando Dados Usando Cursores
const transaction = db.transaction(["clientes"], "readwrite");
const objectStore = transaction.objectStore("clientes");
const request = objectStore.openCursor();
request.onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
if (cursor.value.status === "inativo") {
const updateData = cursor.value;
updateData.status = "ativo";
cursor.update(updateData);
}
cursor.continue();
}
};
Gerenciamento e Atualizações de Esquema
Atualizando o Esquema do Banco de Dados
Quando sua aplicação evolui, você pode precisar modificar o esquema do seu banco de dados:
const request = indexedDB.open("MeuBancoDeDados", 2); // Aumenta o número da versão
request.onupgradeneeded = (event) => {
const db = event.target.result;
// Verifica se a loja de objetos existe
if (!db.objectStoreNames.contains("novaLoja")) {
db.createObjectStore("novaLoja", { keyPath: "id" });
}
};
Migrando Dados Durante Atualizações
request.onupgradeneeded = (event) => {
const db = event.target.result;
const oldVersion = event.oldVersion;
if (oldVersion < 1) {
// Configuração da primeira versão
}
if (oldVersion < 2) {
// Migrar dados para o novo esquema
const transaction = event.target.transaction;
const oldStore = transaction.objectStore("oldStore");
const newStore = db.createObjectStore("novaLoja", { keyPath: "id" });
oldStore.openCursor().onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
newStore.add(cursor.value);
cursor.continue();
}
};
}
};
Otimização de Performance
Operações em Lote Eficientes
Para melhor desempenho, utilize operações em lote ao lidar com múltiplos registros:
const transaction = db.transaction(["clientes"], "readwrite");
const store = transaction.objectStore("clientes");
// Adiciona múltiplos registros em uma única transação
customerList.forEach(cliente => {
store.add(cliente);
});
Aproveitando Índices para Consultas Mais Rápidas
Crie índices em propriedades frequentemente consultadas para garantir uma recuperação de dados mais rápida:
objectStore.createIndex("email", "email", { unique: true });
objectStore.createIndex("ultimoLogin", "ultimoLogin", { unique: false });
Melhores Práticas de Gerenciamento de Conexão
Abra conexões apenas quando necessário e feche-as quando terminar:
let db;
function openDB() {
const request = indexedDB.open("MeuBancoDeDados", 1);
request.onsuccess = (event) => {
db = event.target.result;
};
return request;
}
// Quando você terminar com o banco de dados
function closeDB() {
if (db) {
db.close();
db = null;
}
}
Exemplo do Mundo Real: Gerenciador de Tarefas com Suporte Offline
Configuração do Banco de Dados
const request = indexedDB.open("GerenciadorDeTarefasDB", 1);
request.onupgradeneeded = (event) => {
const db = event.target.result;
const taskStore = db.createObjectStore("tarefas", { keyPath: "id", autoIncrement: true });
// Crie índices para consultas
taskStore.createIndex("status", "status", { unique: false });
taskStore.createIndex("dataVencimento", "dataVencimento", { unique: false });
};
Adicionando Tarefas
function addTask(taskData) {
const transaction = db.transaction(["tarefas"], "readwrite");
const taskStore = transaction.objectStore("tarefas");
return new Promise((resolve, reject) => {
const request = taskStore.add({
title: taskData.title,
description: taskData.description,
status: "pendente",
dueDate: taskData.dueDate,
createdAt: new Date()
});
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
Recuperando e Exibindo Tarefas
function getAllTasks() {
const transaction = db.transaction(["tarefas"], "readonly");
const taskStore = transaction.objectStore("tarefas");
return new Promise((resolve, reject) => {
const request = taskStore.openCursor();
const tarefas = [];
request.onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
tarefas.push(cursor.value);
cursor.continue();
} else {
resolve(tarefas);
}
};
request.onerror = () => reject(request.error);
});
}
Atualizando e Excluindo Tarefas
function updateTaskStatus(id, newStatus) {
const transaction = db.transaction(["tarefas"], "readwrite");
const taskStore = transaction.objectStore("tarefas");
return new Promise((resolve, reject) => {
const getRequest = taskStore.get(id);
getRequest.onsuccess = () => {
const tarefa = getRequest.result;
tarefa.status = newStatus;
tarefa.updatedAt = new Date();
const updateRequest = taskStore.put(tarefa);
updateRequest.onsuccess = () => resolve(true);
updateRequest.onerror = () => reject(updateRequest.error);
};
getRequest.onerror = () => reject(getRequest.error);
});
}
Sincronizando com o Servidor
function syncWithServer() {
if (!navigator.onLine) {
return Promise.reject(new Error("Sem conexão com a internet"));
}
return getAllTasks()
.then(tarefas => {
// Filtrar tarefas que precisam ser sincronizadas
const unsynced = tarefas.filter(tarefa => !tarefa.synced);
// Enviar para o servidor usando a API fetch
return fetch('https://api.exemplo.com/tarefas/sync', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(unsynced)
});
})
.then(response => response.json())
.then(result => {
// Marcar tarefas como sincronizadas
const transaction = db.transaction(["tarefas"], "readwrite");
const taskStore = transaction.objectStore("tarefas");
result.syncedIds.forEach(id => {
const request = taskStore.get(id);
request.onsuccess = () => {
const tarefa = request.result;
tarefa.synced = true;
taskStore.put(tarefa);
};
});
return result;
});
}
// Ouvir eventos online para sincronizar automaticamente
window.addEventListener('online', syncWithServer);
Integrando Apidog para Gerenciamento de API com IndexedDB
Ao construir aplicações com IndexedDB para armazenamento do lado do cliente, você frequentemente precisará interagir com APIs de back-end para sincronização de dados. O Apidog fornece uma solução perfeita para gerenciar essas interações de API.

Por que o Apidog Melhora o Desenvolvimento do IndexedDB
Enquanto você desenvolve aplicações com capacidade offline usando o IndexedDB, o Apidog oferece vários benefícios:
Sincronização em Tempo Real: O Apidog garante que seus endpoints de API usados para sincronização de dados estejam sempre corretamente configurados e testados, eliminando problemas de integração quando sua aplicação vai online.
Respostas de API Falsas: Ao desenvolver funcionalidade offline, os motores de simulação inteligentes do Apidog permitem que você simule respostas de API para testar sua lógica de sincronização do IndexedDB sem um servidor ativo.

Design Colaborativo de API: Quando sua equipe está simultaneamente trabalhando no armazenamento do front-end e nas APIs de back-end, o Apidog facilita a colaboração em tempo real nas especificações da API.

Ao integrar o Apidog em seu fluxo de trabalho de desenvolvimento, você cria uma ponte sem costura entre o armazenamento do lado do cliente e o processamento do lado do servidor, tornando sua aplicação mais robusta e mais fácil de manter.