IndexedDB is a low-level browser API, designed to manage large amounts of data, it is also the only form of storage available for service workers, and is very useful for storing images, audio files and more.
IndexedDB is limited in size, but that limit depends on the browser, for storing blobs bigger than 50MB in firefox you would have to request permission, and in chrome, you get 20%
We are going to create a simple todo application, we will not have to create any UI, I’m solely focusing on the javascript implementation of indexedDB, I will, however, wrap all the calls with promises that return the result and an instance of the DB for easier chaining.
// the example:
if('indexedDB' in window) {
// open the database
openDB(window.indexedDB, 'todos-db')
// add some todos
.then(({ db }) => addTodo(db, 'my todo text 1'))
.then(({ db }) => addTodo(db, 'my todo text 2'))
// get all the todos
.then(({ db }) => getTodos(db))
.then(({ db, event }) => {
const myTodos = event.target.result;
console.log('my todos', myTodos); // will print the todos we entered
// find a todo by text
return findTodo(db, myTodos[0].text);
})
// update the todo and complete it
.then(({ db, todo }) => updateTodo({...todo, complete: true}, todo.id))
// get 1 todo
.then(({ db }) => getTodo(db, 1))
// delete
.then(({ db, event }) => deleteTodo(db, event.target.result.id))
}
// we can use this function to pass the db,
// this way it can be used from the window, or from w web worker.
function openDB(indexedDB, name, version = 1) {
// we open a request for a database
const req = indexedDB.open(name, version);
return new Promise((resolve, reject) => {
// handle if the database needs to be upgraded, or created
req.onupgradeneeded = (e) => {
console.log(`upgrading/creating the database to version: ${version}`);
const thisDB = e.target.result;
// we create a new object store if none exist
if (!thisDB.objectStoreNames.contains("todos")) {
const pushesOS = thisDB.createObjectStore("todos", {
keyPath: "id",
autoIncrement: true
});
// we create indexes, text and completed
pushesOS.createIndex("text", "text", {
unique: false
});
pushesOS.createIndex("complete", "complete", {
unique: false
});
}
};
// handle success
req.onsuccess = event => resolve({
db: e.target.result,
event
});
// handle error
req.onerror = error => reject(error);
});
}
// get all the todos
function getTodos(db) {
return new Promise((resolve, reject) => {
// we need to create a transaction or RW in order to access the store
const transaction = db.transaction(["todos"],"readwrite");
const store = transaction.objectStore("todos");
// get all and handle success and error
const req = store.getAll();
req.onerror = e => reject(e);
req.onsuccess = event => resolve({ event, db });
})
}
// get a single todo
function getTodo(db, id) {
return new Promise((resolve, reject) => {
// we need to create a transaction or RW in order to access the store
const transaction = db.transaction(["todos"],"readwrite");
const store = transaction.objectStore("todos");
// get all and handle success and error
const req = store.get(id);
req.onerror = e => reject(e);
req.onsuccess = e => resolve(e);
})
}
// add a todo
function addTodo(db, text) {
return new Promise((resolve, reject) => {
const transaction = db.transaction(["pushes"], "readwrite");
const store = transaction.objectStore("pushes");
const req = store.add({
text,
completed: false
});
req.onerror = e => reject(e);
req.onsuccess = event => resolve({ event, db });
})
}
// delete a todo
function deleteTodo(db, id) {
return new Promise((resolve, reject) => {
// we need to create a transaction or RW in order to access the store
const transaction = db.transaction(["todos"],"readwrite");
const store = transaction.objectStore("todos");
// delete the todo and handle success and error
const req = store.delete(id);
req.onerror = e => reject(e);
req.onsuccess = e => resolve(e);
})
}
// find a todo by text
function findTodo(db, textToSearch) {
return new Promise((resolve, reject) => {
// we need to create a transaction or RW in order to access the store
const transaction = db.transaction(["todos"],"readonly");
const store = transaction.objectStore("todos");
// we open a cursor to search for the todo
const req = store.openCursor();
req.onerror = e => reject(e);
// success will be called everytime the cursor is iterated
req.onsuccess = e => {
const cursor = event.target.result;
if(cursor) {
const currentTodo = cursor.value;
// check if this is what we looked for
if(currentTodo.text === textToSearch) {
resolve({db, todo: currentTodo})
} else {
// continue iterating
cursor.continue();
}
} else {
// iterated everything and found nothing
reject(new Error("couldn't find the text"))
}
};
})
}
// update a todo
function updateTodo(db, newTodo, id) {
return new Promise((resolve, reject) => {
// we need to create a transaction or RW in order to access the store
const transaction = db.transaction(["todos"],"readwrite");
const store = transaction.objectStore("todos");
// update the todo and handle success and error
// we can also use update
// put => creates a new record if one doesn't exist
const req = store.put(newTodo, id);
req.onerror = e => reject(e);
req.onsuccess = event => resolve({ event, db });
})
}
Liron Navon
A fullstack software engineer
Lazy Engineering
An engineering blog for lazy people