IndexedDB
IndexedDB
Introduction[edit]
Index Database API also known as IndexedDB is a database that is built into the most widely used web-browsers. The common use case of IndexedDB is to store data offline for web applications. To work with the IndexedDB API you need to know JavaScript and async programming with JavaScript.
What is IndexedDB?[edit]
IndexedDB is an NoSQL storage system for the client-side that support also transactions to the usual operations, it is also using indexes for high performance searches (regardless of network availability). Unlike Web storage that supports up to 10MB (5MB by Safari and Opera) IndexedDB data storage limits are large (about 50%) but individually handled by different browsers. A database is private to a domain and subdomain, so any other site can’t access another website IndexedDB stores same-origin-policy.
Features[edit]
- Like other NoSQL databases IndexedDB store key-value pairs meaning that values can be complex structured objects and keys can be properties of those objects. For quick searching and sorted enumeration indexes can be created by any property the objects.
- IndexedDB is mostly asynchrone meaning that every operation is represented by a request. So it doesn’t block the application during an operation. IndexedDB uses a lot of requests. After each request you get a notified by an DOM event whether the operation was successful or not after that you can access the result or error code by an event listener.
- IndexedDB is a transactional database. So you can’t execute operations outside of a transaction in IndexedDB. Transactions have a well-defined lifetime. When a transaction finishes, it is committed automatically. If an operation fails, all the operations in the transaction are rolled back and the state goes back to a known state.
- IndexedDB is a object-oriented database, instead of tables, rows and columns IndexedDB use object stores which is similar to tables in a traditional relational database. In this object stores you store key - value pairs as earlier mentioned.The values can be any JavaScript type including boolean, number, string, undefined, null, date, object, array, regex, blob, and files.
Downsides[edit]
- The browser can wipe out the data by user request, private browsing and when disk limit is reached.
- There is no equivalent to the ‘LIKE’ operator in SQL for full text search.
- For synchronising the server-side database with the client-side IndexedDB you have to write the code by your own because IndexedDB doesn’t take care of it.
Create a db[edit]
We will create a contact book that stores your contact name and email into the IndexedDB. For this example i will use:
HTML[edit]
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <style> </style> <title>IndexedDB - Example</title> </head> <body> <h1>Contacts </h1> <form action="" id="addForm"> <label for="name">Name:</label> <input type="text" id="name" required name="name" placeholder="Max Mustermann"> <br> <label for="email">Email:</label> <input type="email" id="email" required name="email" placeholder="max@example.com"> <br> <button type="submit" id="add">Add</button> <button type="submit" id="update">Update</button> <button type="submit" id="delete">Delete</button> </form> <h1>Contact list</h1> <p>You can focus the contact by clicking for updating!</p> <ul class="contactlist"> </ul> <script src="index.js"></script> </body> </html>
JavaScript[edit]
// we check if IndexedDB is supported by the browser if (!window.indexedDB) { console.log(`Your browser doesn't support IndexedDB`); } // after that now let us create a db // later we will save our DB into this variable let db = null let contactsStore = null // With the open method we will create a DB if it not exists already or open an DB that exists, // we will pass the name of the DB and verison number(if the DB does not exist, it automatically starts at 1) into the open method. const openDBReq = indexedDB.open('ContactBookDB',4) // The following three events onerror,onsuccess and onupgradeneeded. // We are always going to have within our code. // This event will raise if a error occurred while trying to open the DB openDBReq.onerror = (err) => { console.log('A problem occurred',err) } // This event will raise if the DB has been opened and the version has not changed openDBReq.onsuccess = (event) =>{ db = event.target.result buildContactList('contacts','readonly') console.log('success',db) } // This event will raise when you open the DB the first time // or a new version was passed into the open method. openDBReq.onupgradeneeded = (event) => { db = event.target.result console.log('created or upograded',db) // we can access the old and new version number let oldVersNo = event.oldVersion let newVersNo = event.newVersion console.log(`Updated DB from version ${oldVersNo} to verison ${newVersNo}`); // before you create a object store we need to check if it exists already in the db // without checking we will raise an error if(!db.objectStoreNames.contains('contacts')) { // with createObjectStore you create an object store where the key-value pairs are stored // in this case the email is an unique value for the index contactsStore = db.createObjectStore('contacts',{ keyPath: "email" }) } } let nameInputElement = document.querySelector('#name') let emailInputElement = document.querySelector('#email') let addBtn = document.querySelector('#add') let contact = { name, email, } // add contact addBtn.addEventListener('click',(event) => { event.preventDefault() // input value check if (nameInputElement.value === '' && emailInputElement.value === '' || emailInputElement.value.indexOf('@') === -1) { return } let contact = { name : nameInputElement.value.trim(), email : emailInputElement.value.trim() } console.log(contact.name, contact.email); // here we define an transaction with one request(operation) // to add a new contact const transaction = makeTransaction('contacts','readwrite') transaction.oncomplete = (event) => { nameInputElement.value = '' emailInputElement.value = '' buildContactList() console.log('transaction successed',event) } // now we fill our transaction with one request // if one request fails the transaction fails too // in this case we send a alert to the user that the email address // exists already const store = transaction.objectStore('contacts') const addReq = store.add(contact) addReq.onsuccess = (event) => { nameInputElement.value = '' emailInputElement.value = '' contact.name = '' contact.email = '' console.log('request successed',event) } // every email address need to be unique addReq.onerror = (err) => { alert('Email exists alredy in your contacts!') } }) // update contact let updateBtn = document.querySelector('#update') updateBtn.addEventListener('click',(event)=>{ event.preventDefault() if (nameInputElement.value === '' && emailInputElement.value === '' || emailInputElement.value.indexOf('@') === -1) { return } let contact = { name : nameInputElement.value.trim(), email : emailInputElement.value.trim() } if (emailInputElement) { const transaction = makeTransaction('contacts','readwrite') transaction.oncomplete = (event) => { nameInputElement.value = '' emailInputElement.value = '' contact.name = '' contact.email = '' buildContactList() } const store = transaction.objectStore('contacts') const updateReq = store.put(contact) updateReq.onsuccess = (event) => { console.log('update successfull',event) } // every email address need to be unique updateReq.onerror = (err) => { console.log(err) } } }) // select contact document.querySelector('.contactlist').addEventListener('click',(event) =>{ let listElement = event.target.closest('[id]') let email = listElement.getAttribute('id') console.log(email,listElement) const transaction = makeTransaction('contacts','readonly') transaction.oncomplete = (event) => { } let store = transaction.objectStore('contacts') let getReq = store.get(email) getReq.onsuccess = (event) => { let contact = event.target.result document.querySelector('#name').value = contact.name document.querySelector('#email').value = contact.email } getReq.onerror = (err) => { console.log(err) } }) // delete contact let deleteBtn = document.querySelector('#delete') deleteBtn.addEventListener('click',(event) => { event.preventDefault() if (emailInputElement.value){ const transaction = makeTransaction('contacts','readwrite') transaction.oncomplete = (event) => { nameInputElement.value = '' emailInputElement.value = '' contact.name = '' contact.email = '' buildContactList() } const store = transaction.objectStore('contacts') const deleteReq = store.delete(emailInputElement.value) deleteReq.onsuccess = (event) => { console.log('delete successfull',event) } // every email address need to be unique deleteReq.onerror = (err) => { console.log(err) } } }) // we will wrap the transaction object and the error listener into a function // because this will not change and we need it in every request function makeTransaction(objectStore, mode) { const transaction = db.transaction(objectStore,mode) transaction.onerror = (err) => { console.log('Failure rollback',err) } return transaction } // here we will use the getAll method to get an array of objects // and display the result function buildContactList(){ let contactListElement = document.querySelector('.contactlist') //contactListElement.innerHTML =`<li> Loading...</li>` const transaction = makeTransaction('contacts','readonly') transaction.oncomplete = (event) => { console.log('transaction successed', event) } let store = transaction.objectStore('contacts') let getAllReq = store.getAll() getAllReq.onsuccess = (event) => { contactListElement.innerHTML = event.target.result.map((contact) =>{ return `<li id=${contact.email}><span>Name: ${contact.name}</span> Email: ${contact.email}</li>` }).join('\n') console.log(event.target.result) } getAllReq.onerror = (err) => { console.log('request failure',err) } }
IndexedDB Promised Library vs IndexedDB API[edit]
IndexedDB can also be used with the concept of promises, thanks to Jake Archibald.
Instead of calling the request.onsucess, request.onerror events we now able to interact with the request by promise chains.
IndexedDB Promised | IndexedDB API | |
---|---|---|
Open database | idb.open(name, version, upgradeCallback) | indexedDB.open(name, version) |
Upgrade database | Inside upgradeCallback | request.onupgradeneeded |
Success | .then | request.onsuccess |
Error | .catch | request.onerror |
Weblinks[edit]
- https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/ IndexedDB API Docs by MDN.
- https://www.freecodecamp.org/news/a-quick-but-complete-guide-to-indexeddb-25f030425501/ A quick guid for the IndexedDB Promised library.
- https://developers.google.com/web/ilt/pwa/working-with-indexeddb A guide by google PWA training.
- https://github.com/jakearchibald/idb IndexedDB Promised Library