IndexedDB is useful if you need to store large amounts of data on the client side that can quickly be retrieved.
Why use IndexedDB over local storage?
IndexedDB is a NoSQL database that stores data in object stores in a binary format for efficiency. There is no inbuilt functionality for schemas.
IndexedDB allows for
IDB is a library that makes it easier to work with indexedDB. Instead of callbacks it uses promises. npm install idb
// Get the indexedDB which will be different depending upon the platform.
const indexedDB =
window.indexedDB ||
window.mozIndexedDB ||
window.webkitIndexedDB ||
window.msIndexedDB ||
window.shimIndexedDB
// Open the db
const dbName = "Library"
const version = 1 // Version of indexedDB
const request = indexedDB.open(dbName, version)
// Handle error
request.onerror = function(event){
console.error("An error occurred with IndexedDB")
console.error(event)
}
// When a new db is created or an existing db's version number has upgraded
// This function allows you to specify the schema of the DB
request.onupgradeneeded = function(){
const db = request.result
// Define a new object store in the indexedDB
const objectStoreName = "FictionalBooks"
const primaryKey = "ISBN"
const fictionalBooks = db.createObjectStore(objectStoreName, { keyPath: primaryKey }) // You can have the optional ,autoIncrement: true to automatically increment the primaryKey
}
import {openDB} from "idb"
async function initDB(){
const dbName = "Library"
const version = 1
openDB(dbName, version, {
upgrade: function(db){
const objectStoreName = "FictionalBooks"
// Make sure db doesn't already exist
if(!db.objectStoreNames.contains(objectStoreName)){
db.createObjectStore(objectStoreName, { keyPath: "ISBN"/*, autoIncrement: true*/})
}
}
})
}
Method Name | Description |
---|---|
.add(object) | Adds a new object to the object store. |
.get(primary key) | Gets object from the object store or index. |
.getAll() | Gets all the objects in the object store. |
.put({primaryKey: 1, object}) | Replaces object with the primary key in the object store. |
.delete(primary key) | Deletes object with primary key. |
.clear() | Deletes all objects in object store. |
.count(query) | Gets the count of the query |
.count() | Counts all objects in the object store. |
You can do deleteObjectStore("objectStoreName")
to delete object stores.
const data = [
{
ISBN: 9781234567, Title: "Book A", Author: "Author A", Genre: "Sci-Fi"
},
{
ISBN: 9781234568, Title: "Book B", Author: "Author B", Genre: "Fantasy"
},
{
ISBN: 9781234569, Title: "Book C", Author: "Author A", Genre: "Fantasy"
},
]
data.forEach(book => {
fictionalBooks.add(book) // Adds book object to the fictional books object store
})
This is the boilerplate code needed to make any CRUD operation
// Open the db
const request = indexedDB.open("Library", 1)
// Handle if request what successful. Opened db
request.onsuccess = function(event){
const db = event.target.result
// Make the transaction
const objectStores = "FictionalBooks" // String or array of strings of object stores
const mode = "readwrite" // "readonly", "readwrite", or "versionchange". This is optional and the default value is readonly
const transaction = db.transaction(objectStores, mode)
// Get object store from transaction
const fictionalBooks = transaction.objectStore("FictionalBooks")
// Make request for CRUD operation
const crudRequest = fictionalBooks.getAll()
// Handle if the request succeeded
crudRequest.onsuccess = function(event){
const books = event.target.result
console.log(books)
}
// Close db once the request completed
crudRequest.oncomplete = () => {
db.close()
}
}
// Handle if the request failed. Failed to open db
request.onerror = (event) => {
console.error(`Error opening the database: ${event.target.error}` )
}
This is the boilerplate code needed to make any CRUD operation
import {openDB} from "idb"
async function crudOperation(){
const db = await openDB("Library", 1)
try{
const transaction = db.transaction("FictionalBooks", "readwrite")
const fictionalBooks = transaction.objectStore("FictionalBooks")
const books = await fictionalBooks.getAll()
console.log(books)
}catch(error){
console.error(`An error occurred with get: ${error}`)
}finally{
db.close()
}
}
Or you can make it a function
async function(db){
try{
const transaction = db.transaction("FictionalBooks", "readwrite")
const fictionalBooks = transaction.objectStore("FictionalBooks")
const books = await fictionalBooks.getAll()
console.log(books)
return transaction.complete // returns a promise
}catch(error){
console.error(`An error occurred with get: ${error}`)
}
}
To get more complex queries you have to use indexes.
// Creating indexes
const indexName = "AuthorIndex"
const keys = "Author" // String or array of strings of the keys that are to be indexed
objectStore.createIndex(indexName, keys, { unique: false }) // Enforces uniqueness for any new objects added to the object store
// Getting indexes
const index = objectStore.index(indexName)
// Deleting indexes
objectStore.deleteIndex(indexName)
A cursor allows you to iterate over multiple objects in an object store or index.
// The range is optional
// Sets the range for what the primary key can be for when the cursor moves
let range = IDBKeyRange.bound(100/*lower*/, 200/*upper*/, false/*Optional. Inclusive lower?*/, true/*Optional. Inclusive upper?*/) // The default for inclusive lower and inclusive upper are false
range = IDBKeyRange.lowerBound(100)
range = IDBKeyRange.upperBound(200)
range = IDBKeyRange.only(1) // The cursor can only move between queries where it's primary key is equal to 1
// The direction is optional
const direction = "next" // "nextunique" which moves to the next unique object in an index, "prev", and "prevunique"
const cursor = await objectStore.openCursor(range, direction)
// Move the cursor
cursor.continue()
// Delete cursor
cursor.close()
Dexie is a library to make working with indexedDb easier to use.
npm install dexie
Creating a db
let db = new Dexie("Database name")
db.version(1).stores({
table1: "++id,field1,field2",
table2: "++id,field1,field2"
})
Creating data
const newlyAddedObjId = await db.table1.add({
field1: "value1",
field1: "value2"
})
Reading data
let id = 1 // Ids start at 1
let obj = await db.table1.get(id)
// or
let table1Objs = await db.table.toArray()
Updating data
let id = 1
await db.table1.update(id, {
field1: "updated value"
})
// You can update just one value at a time
Deleting data
// Delete 1 obj in table
let id = 1
await db.table1.delete(1)
// Delete all objects inside table
await db.table1.clear()
// Delete table
await db.delete("table1")
You can have something like state in react that updates whenever the db updates.
npm install dexie-react-hooks
export default function Component({db}){
const table1 = useLiveQuery(async () => {
return await db.table1.toArray()
}, [/*optional dependencies*/])
return <div>{table1}</div>
}
Whenever the db changes table1 gets updated like state in react.
If you are using custom awaits, awaits not associated with dexie, you have to use Promise.all in order for it to work.