Sign in
Log inSign up
IndexedDB

IndexedDB

Liron Navon's photo
Liron Navon
·Jan 26, 2019

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

An engineering blog for lazy people