What is idb?
The idb npm package is a small library that mirrors IndexedDB, but features a more user-friendly API. It allows developers to interact with IndexedDB using promises instead of events, which simplifies the code and improves readability and maintainability. It's particularly useful for applications that need to store large amounts of data on the client side.
What are idb's main functionalities?
Database Creation and Versioning
This code sample demonstrates how to create a new IndexedDB database and an object store within it using idb. The `openDB` function is used to open a database, and it takes the database name, version number, and an upgrade callback as arguments. The upgrade callback is executed when the database is created or upgraded.
const dbPromise = idb.openDB('my-database', 1, {
upgrade(db) {
db.createObjectStore('keyval');
}
});
Adding and Retrieving Data
This code sample shows how to add data to and retrieve data from an object store in IndexedDB using idb. The `addData` function creates a transaction, accesses the object store, and uses the `put` method to add data. The `getData` function retrieves data using the `get` method.
const dbPromise = idb.openDB('my-database', 1);
async function addData(key, val) {
const db = await dbPromise;
const tx = db.transaction('keyval', 'readwrite');
const store = tx.objectStore('keyval');
store.put(val, key);
await tx.done;
}
async function getData(key) {
const db = await dbPromise;
return db.transaction('keyval').objectStore('keyval').get(key);
}
Cursor Iteration
This code sample illustrates how to iterate over entries in an object store using a cursor. The `iterateCursor` function opens a read-only transaction, accesses the object store, and uses `openCursor` to start the iteration. The cursor is advanced using the `continue` method.
const dbPromise = idb.openDB('my-database', 1);
async function iterateCursor() {
const db = await dbPromise;
const tx = db.transaction('keyval', 'readonly');
const store = tx.objectStore('keyval');
let cursor = await store.openCursor();
while (cursor) {
console.log(cursor.key, cursor.value);
cursor = await cursor.continue();
}
}
Other packages similar to idb
dexie
Dexie.js is a wrapper library for IndexedDB that allows for simpler syntax and complex queries. It provides a more powerful query language and schema handling compared to idb, which makes it suitable for more complex applications that require advanced database features.
pouchdb
PouchDB is an open-source JavaScript database inspired by Apache CouchDB that is designed to run well within the browser. It enables applications to store data locally while offline, then synchronize it with CouchDB and compatible servers when the application is back online, unlike idb which is purely a client-side solution.
IndexedDB Promised
This is a tiny library that mirrors IndexedDB, but replaces the weird IDBRequest
objects with promises, plus a couple of other small changes.
Examples
Keyval Store
This is very similar to localStorage
, but async. If this is all you need, you may be interested in idb-keyval, you can always upgrade to this library later.
const dbPromise = idb.open('keyval-store', 1, upgradeDB => {
upgradeDB.createObjectStore('keyval');
});
const idbKeyval = {
get(key) {
return dbPromise.then(db => {
return db.transaction('keyval')
.objectStore('keyval').get(key);
});
},
set(key, val) {
return dbPromise.then(db => {
const tx = db.transaction('keyval', 'readwrite');
tx.objectStore('keyval').put(val, key);
return tx.complete;
});
},
delete(key) {
return dbPromise.then(db => {
const tx = db.transaction('keyval', 'readwrite');
tx.objectStore('keyval').delete(key);
return tx.complete;
});
},
clear() {
return dbPromise.then(db => {
const tx = db.transaction('keyval', 'readwrite');
tx.objectStore('keyval').clear();
return tx.complete;
});
},
keys() {
return dbPromise.then(db => {
const tx = db.transaction('keyval');
const keys = [];
const store = tx.objectStore('keyval');
(store.iterateKeyCursor || store.iterateCursor).call(store, cursor => {
if (!cursor) return;
keys.push(cursor.key);
cursor.continue();
});
return tx.complete.then(() => keys);
});
}
};
Usage
keyValStore.set('foo', {hello: 'world'});
keyValStore.get('foo').then(val => console.log(val));
Set of objects
Imagine we had a set of objects like…
{
"id": 123456,
"data": {"foo": "bar"}
}
Upgrading existing DB
const dbPromise = idb.open('keyval-store', 2, upgradeDB => {
switch (upgradeDB.oldVersion) {
case 0:
upgradeDB.createObjectStore('keyval');
case 1:
upgradeDB.createObjectStore('objs', {keyPath: 'id'});
}
});
Adding
dbPromise.then(db => {
const tx = db.transaction('objs', 'readwrite');
tx.objectStore('objs').put({
id: 123456,
data: {foo: "bar"}
});
return tx.complete;
});
Getting all
dbPromise.then(db => {
return db.transaction('objs')
.objectStore('objs').getAll();
}).then(allObjs => console.log(allObjs));
Getting by ID
dbPromise.then(db => {
return db.transaction('objs')
.objectStore('objs').get(123456);
}).then(obj => console.log(obj));
Limitations
Transaction lifetime
At time of writing, all browsers aside from Chrome don't treat promise callbacks as microtasks, or call microtasks incorrectly. This means transactions end by the time promise callbacks are called. In practice, this means you cannot perform transactions that involve waiting for a value, then using it within the same transaction.
const tx = db.transaction('store', 'readwrite');
const store = tx.objectStore('store');
store.get('hello').then(val => store.put(val, 'foo'));
The above will fail in browsers other than Chrome, because the transaction has closed by the time we get to the .put
.
You can work around this in Firefox by using a promise polyfill that correctly uses microtasks, such as es6-promise.
Safari
This is a simple wrapper library, so you're exposed to bugs in the underlying implementation. Unfortunately Safari has a lot of these.
API
idb
This is your entry point to the API. It's exposed to the global scope unless you're using a module system such as browserify, in which case it's the exported object.
idb.open(name, version, upgradeCallback)
This method returns a promise that resolves to a DB
.
name
and version
behave as they do in indexedDB.open
.
upgradeCallback
is called if version
is greater than the version last opened. It's similar to IDB's onupgradeneeded
. The callback receives an instance of UpgradeDB
.
idb.open('keyval-store', 2, upgradeDB => {
switch (upgradeDB.oldVersion) {
case 0:
upgradeDB.createObjectStore('keyval');
case 1:
upgradeDB.createObjectStore('stuff', {keyPath: ''});
}
}).then(db => console.log("DB opened!", db));
idb.delete(name)
Behaves like indexedDB.deleteDatabase
, but returns a promise.
idb.delete('keyval-store').then(() => console.log('done!'));
DB
Properties:
- Same as equivalent properties on an instance of
IDBDatabase
:
name
version
objectStoreNames
Methods:
close
- as idbDatabase.close
transaction
- as idbDatabase.transaction
, but returns a Transaction
UpgradeDB
As DB
, except:
Properties:
transaction
- this is a property rather than a method. It's a Transaction
representing the upgrade transactionoldVersion
- the previous version of the DB seen by the browser, or 0 if it's new
Methods:
createObjectStore
- as idbDatabase.createObjectStore
, but returns an ObjectStore
deleteObjectStore
- as idbDatabase.deleteObjectStore
Transaction
Properties:
complete
- a promise. Resolves when transaction completes, rejects if transaction aborts or errors- Same as equivalent properties on an instance of
IDBTransaction
:
Methods:
abort
- as idbTransaction.abort
objectStore
- as idbTransaction.objectStore
, but returns an ObjectStore
idb.open('keyval-store', 1, upgradeDB => {
switch (upgradeDB.oldVersion) {
case 0:
upgradeDB.createObjectStore('keyval');
}
}).then(db => {
const tx = db.transaction('keyval', 'readwrite');
tx.objectStore('keyval').put('hello', 'world');
return tx.complete;
}).then(() => console.log("Done!"));
ObjectStore
Properties:
- Same as equivalent properties on an instance of
IDBObjectStore
:
name
keyPath
indexNames
autoIncrement
Methods:
- Same as equivalent methods on an instance of
IDBObjectStore
, but returns a promise that resolves/rejects based on operation success/failure:
put
add
delete
clear
get
getAll
getAllKeys
count
- Same as equivalent methods on an instance of
IDBObjectStore
, but returns a promise that resolves with a Cursor
:
deleteIndex
- as idbObjectStore.deleteIndex
- Same as equivalent methods on an instance of
IDBObjectStore
, but returns an Index
:
iterateCursor
- see belowiterateKeyCursor
- see below
iterateCursor
& iterateKeyCursor
Due to the microtask issues in some browsers, iterating over a cursor using promises doesn't always work:
const tx = db.transaction('stuff');
tx.objectStore('stuff').openCursor().then(function cursorIterate(cursor) {
if (!cursor) return;
console.log(cursor.value);
return cursor.continue().then(cursorIterate);
});
tx.complete.then(() => console.log('done'));
So in the mean time, iterateCursor
and iterateKeyCursor
map to openCursor
& openKeyCursor
, take identical arguments, plus an additional callback that receives an IDBCursor
, so the above example becomes:
const tx = db.transaction('stuff');
tx.objectStore('stuff').iterateCursor(cursor => {
if (!cursor) return;
console.log(cursor.value);
cursor.continue();
});
tx.complete.then(() => console.log('done'));
The intent is to remove iterateCursor
and iterateKeyCursor
from the library once browsers support promises and microtasks correctly.
Index
Properties:
- Same as equivalent properties on an instance of
IDBIndex
:
name
keyPath
multiEntry
unique
Methods:
- Same as equivalent methods on an instance of
IDBIndex
, but returns a promise that resolves/rejects based on operation success/failure:
get
getKey
getAll
getAllKeys
count
- Same as equivalent methods on an instance of
IDBIndex
, but returns a promise that resolves with a Cursor
:
iterateCursor
- as objectStore.iterateCursor
but over the indexiterateKeyCursor
- as objectStore.iterateKeyCursor
but over the index
Cursor
Properties:
- Same as equivalent properties on an instance of
IDBCursor
:
direction
key
primaryKey
value
Methods:
- Same as equivalent methods on an instance of
IDBCursor
, but returns a promise that resolves/rejects based on operation success/failure:
- Same as equivalent methods on an instance of
IDBCursor
, but returns a promise that resolves with a Cursor
:
advance
continue
continuePrimaryKey