
O IndexedDB é uma das soluções de armazenamento que os navegadores foram introduzindo ao longo dos anos.
Trata-se de uma base de dados chave/valor (uma base de dados noSQL) considerada a solução definitiva para guardar dados no navegador.
É uma API assíncrona, o que significa que a execução do código não bloqueia o resto do teu programa, com o ganho de experiência que isso traz aos utilizadores. Pode guardar uma quantidade indefinida de dados, embora, acima de um certo limiar, o utilizador seja convidado a autorizar o site a guardar mais.
Há outros métodos, os cookies e o Web Storage ( localStorage e sessionStorage ). O armazenamento local/de sessão tem o inconveniente de estar limitado a um tamanho pequeno: os navegadores dão de 2 MB a 10 MB de espaço por site.
Embora tecnicamente possas criar várias bases de dados por site, o habitual é criar uma só e, dentro dela, criar vários object stores.
Uma base de dados é privada de um domínio, pelo que nenhum outro site consegue aceder a uma base IndexedDB que não seja a sua.
Cada object store contém normalmente um conjunto de coisas, que podem ser:
- objects
- arrays
- strings
- numbers
- dates
Por exemplo, podes ter um store que contém mensagens e outro que contém comentários.
Um store contém um certo número de objetos que possuem uma chave única, que representa a forma como um objeto pode ser identificado.
Podes modificar esses objetos através de transações, executando operações de adição, alteração, remoção e iteração sobre os elementos que contêm.
Desde a chegada das Promises no ES6, e da passagem subsequente das APIs para as promessas, a API IndexedDB parece um pouco da velha guarda.
Não há mal nenhum nisso, mas em todos os exemplos que vou explicar uso a biblioteca de promessas IndexedDB do Jake Archibald, uma camada fina sobre a API IndexedDB que a torna mais fácil de usar.
Esta biblioteca é também a usada em todos os exemplos sobre IndexedDB no site Google Developers
Criar uma base de dados IndexedDB
A forma mais simples é usar o unpkg, acrescentando-o ao cabeçalho da página:
<script type="module">
import { openDB, deleteDB } from 'https://unpkg.com/idb?module'
</script>Antes de usares a API IndexedDB, verifica sempre se o navegador é compatível. Mesmo estando amplamente disponível, nunca sabes que navegador o utilizador tem:
(async () => {
'use strict'
if (!('indexedDB' in window)) {
console.warn('IndexedDB not supported')
return
}
})()Como criar uma base de dados IndexedDB
Usa o 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)
}
})
})()Os 2 primeiros parâmetros são o nome da base de dados e a versão. O terceiro parâmetro, facultativo, é um objeto que contém uma função chamada apenas se o número de versão for superior ao da base de dados instalada. No corpo dessa função podes atualizar a estrutura (stores e índices) da base de dados.
Adicionar dados a um store
Adicionar dados na criação do store, ao inicializá-lo
Para adicionar dados usa-se o método put do object store, mas primeiro é preciso inicializá-lo com db.createObjectStore() no momento em que o criamos.
Com o put, o valor é o primeiro argumento e a chave é o segundo. Isto acontece porque, se indicares a keyPath ao criar o object store, deixas de precisar de escrever o nome da chave em cada put(): basta o valor.
Isto preenche o store0 logo depois de o termos criado:
(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')
}
})
})()Adicionar dados a um store já criado, com transações
Para acrescentar elementos mais tarde, tens de criar uma transação de leitura/escrita, que garante a integridade da base de dados (se uma operação falhar, todas as operações da transação são anuladas e o estado volta ao inicial).
Para isso, usa uma referência ao objeto dbPromise que obtivemos ao chamar o openDB e executa:
(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
})()Obter dados de um store
Obter um item de um object store: get()
const key = 'Hello again'
const item = await db.transaction(storeName).objectStore(storeName).get(key)Obter todos os itens de um object store: getAll()
Obter todas as chaves guardadas
const items = await db.transaction(storeName).objectStore(storeName).getAllKeys()Obter todos os valores guardados
const items = await db.transaction(storeName).objectStore(storeName).getAll()Apagar dados do IndexedDB
Apagar a base de dados, um object store e dados
Apagar por completo uma base de dados IndexedDB
const dbName="mydbname"
await deleteDB(dbName)Apagar dados dentro de um object store
Usamos uma transação:
(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 a partir de uma versão anterior da base de dados
O terceiro parâmetro (facultativo) da função openDB() é um objeto que pode conter uma função de atualização chamada apenas se o número de versão for superior ao da base de dados instalada. No corpo dessa função podes atualizar a estrutura (stores e índices) da base de dados:
const name="mydbname"
const version = 1
openDB(name, version, {
upgrade(db, oldVersion, newVersion, transaction) {
console.log(oldVersion)
}
})Neste callback podes verificar a partir de que versão o utilizador está a atualizar e executar operações em função disso.
Podes fazer uma migração a partir de uma versão anterior da base de dados com esta sintaxe
(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:
// um store introduzido na versão 1
db.createObjectStore('store1')
case 1:
// apagar o antigo store na versão 2 e criar um novo
db.createObjectStore('store2', { keyPath: 'name' })
}
db.createObjectStore(storeName)
}
})
})()Chave única
O createObjectStore(), como vês no caso 1, aceita um segundo parâmetro que indica a chave de índice da base de dados. É muito útil quando guardas objetos: as chamadas a put() não precisam de um segundo parâmetro, podem receber apenas o valor (um objeto) e a chave será mapeada para a propriedade do objeto que tem esse nome.
O índice dá-te uma forma de recuperar um valor mais tarde por essa chave específica, e tem de ser única (cada elemento tem de ter uma chave diferente).
Uma chave pode ficar em incremento automático, o que te dispensa de a gerir no código do cliente:
db.createObjectStore('notes', { autoIncrement: true })Usa o incremento automático se os teus valores ainda não contiverem uma chave única.
Verificar se um object store existe
Podes verificar se um object store já existe chamando o método objectStoreNames():
const storeName="store1"
if (!db.objectStoreNames.contains(storeName)) {
db.createObjectStore(storeName)
}Apagar do IndexedDB
Apagar a base de dados, um object store e dados
Apagar uma base de dados
await deleteDB('mydb')Apagar um object store
Um object store só pode ser apagado no callback de abertura de uma db, e esse callback só é chamado se indicares uma versão superior à que está instalada:
const db = await openDB('dogsdb', 2, {
upgrade(db, oldVersion, newVersion, transaction) {
switch (oldVersion) {
case 0:
// um store introduzido na versão 1
db.createObjectStore('store1')
case 1:
// apagar o antigo store na versão 2 e criar um novo
db.deleteObjectStore('store1')
db.createObjectStore('store2')
}
}
})Para apagar dados de um object store, usa uma transação
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

