Build a basic web app with IndexedDB

Andy Haskell
Aug 1, 2018 · 16 min read

Why should I use IndexedDB in my web app?

Making our database

let db;
let dbReq = indexedDB.open('myDatabase', 1);
dbReq.onupgradeneeded = function(event) {
// Set the db variable to our database so we can use it!
db = event.target.result;

// Create an object store named notes. Object stores
// in databases are where data are stored.
let notes = db.createObjectStore('notes', {autoIncrement: true});
}
dbReq.onsuccess = function(event) {
db = event.target.result;
}
dbReq.onerror = function(event) {
alert('error opening database ' + event.target.errorCode);
}
<!DOCTYPE html>
<html>
<head><title>IndexedDB note store</title></head>
<body>
<div id="app"><h1>Coming soon</h1></div>
<script src="db.js"></script>
</body>
</html>
let db;
let dbReq = indexedDB.open('myDatabase', 1);
dbReq.onupgradeneeded = function(event) {
db = event.target.result;
let notes = db.createObjectStore('notes', {autoIncrement: true});
}
dbReq.onsuccess = function(event) {
db = event.target.result;
}
dbReq.onerror = function(event) {
alert('error opening database ' + event.target.errorCode);
}

Put some data into the database

function addStickyNote(db, message) {
// Start a database transaction and get the notes object store
let tx = db.transaction(['notes'], 'readwrite');
let store = tx.objectStore('notes');
// Put the sticky note into the object store
let note = {text: message, timestamp: Date.now()};
store.add(note);
// Wait for the database transaction to complete
tx.oncomplete = function() { console.log('stored note!') }
tx.onerror = function(event) {
alert('error storing note ' + event.target.errorCode);
}
}
dbReq.onsuccess = function(event) {
db = event.target.result;
// Add some sticky notes
addStickyNote(db, 'Sloths are awesome!');
addStickyNote(db, 'Order more hibiscus tea');
addStickyNote(db, 'And Green Sheen shampoo, the best for sloth fur algae grooming!');

}
let tx = db.transaction(['notes'], 'readwrite');
let store = tx.objectStore('notes');
let note = {text: message, timestamp: Date.now()};
store.add(note);
tx.oncomplete = function() { console.log('stored note!') }
tx.onerror = function(event) {
alert('error storing note ' + event.target.errorCode);
}
let notes = db.createObjectStore('notes', {autoIncrement: true});
<!DOCTYPE html>
<html>
<head><title>IndexedDB note store</title></head>
<body>
<div id="app">
<div id="textbox">
<textarea id="newmessage"></textarea>
<button onclick="submitNote()">Add note</button>
</div>

</div>
<script src="db.js"></script>
</body>
</html>
function submitNote() {
let message = document.getElementById('newmessage');
addStickyNote(db, message.value);
message.value = '';
}

Transactions are king in IndexedDB

function addManyNotes(db, messages) {
let tx = db.transaction(['notes'], 'readwrite');
let store = tx.objectStore('notes');
for (let i = 0; i < messages.length; i++) { // All of the requests made from store.add are part of
// the same transaction
store.add({text: messages[i], timestamp: Date.now()});
} tx.oncomplete = function() {console.log('transaction complete')};
}
let tx = db.transaction(['notes', 'someOtherStore'], 'readwrite');
// These transactions can all do their thing at the same time!
let tx = db.transaction(['notes', 'someOtherStore'], 'readonly');
let tx2 = db.transaction(['notes'], 'readonly');
let tx3 = db.transaction(['someOtherStore'], 'readonly');

Retrieving data and displaying your sticky notes

// Set up an object store and transaction
let tx = db.transaction(['notes'], 'readonly');
let store = tx.objectStore('notes');
// Set up a request to get the sticky note with the key 1
let req = store.get(1);
// We can use the note if the request succeeds, getting it in the
// onsuccess handler
req.onsuccess = function(event) {
let note = event.target.result;
if (note) {
console.log(note);
} else {
console.log("note 1 not found")
}
}
// If we get an error, like that the note wasn't in the object
// store, we handle the error in the onerror handler
req.onerror = function(event) {
alert('error getting note 1 ' + event.target.errorCode);
}
function getAndDisplayNotes(db) {
let tx = db.transaction(['notes'], 'readonly');
let store = tx.objectStore('notes');
// Create a cursor request to get all items in the store, which
// we collect in the allNotes array
let req = store.openCursor();
let allNotes = [];

req.onsuccess = function(event) {
// The result of req.onsuccess is an IDBCursor
let cursor = event.target.result;
if (cursor != null) { // If the cursor isn't null, we got an IndexedDB item.
// Add it to the note array and have the cursor continue!
allNotes.push(cursor.value);
cursor.continue();
} else { // If we have a null cursor, it means we've gotten
// all the items in the store, so display the notes we got
displayNotes(allNotes);
}
}
req.onerror = function(event) {
alert('error in cursor request ' + event.target.errorCode);
}
}
let cursor = event.target.result;if (cursor != null) {
allNotes.push(cursor.value);
cursor.continue();
} else {
} else {
displayNotes(allNotes);
}
req.onsuccess = function(event) {
<div id="notes"></div>
function displayNotes(notes) {
let listHTML = '<ul>';
for (let i = 0; i < notes.length; i++) {
let note = notes[i];
listHTML += '<li>' + note.text + ' ' +
new Date(note.timestamp).toString() + '</li>';
}
document.getElementById('notes').innerHTML = listHTML;
}
dbReq.onsuccess = function(event) {
db = event.target.result;
getAndDisplayNotes(db);
}
tx.oncomplete = function() { getAndDisplayNotes(db); }

Indices, putting the indexed in IndexedDB

// We update the version of the database to 2 to trigger
// onupgradeneeded
let dbReq = indexedDB.open('myDatabase', 2);
dbReq.onupgradeneeded = function(event) {
db = event.target.result;
// Create the nores object store, or retrieve it if it
// already exists.
let notes;
if (!db.objectStoreNames.contains('notes')) {
notes = db.createObjectStore('notes', {autoIncrement: true});
} else {
notes = dbReq.transaction.objectStore('notes');
}
// If there isn't already a timestamp index, make one so we
// can query notes by their timestamps
if (!notes.indexNames.contains('timestamp')) {
notes.createIndex('timestamp', 'timestamp');
}

}
let dbReq = indexedDB.open('myDatabase', 2);
if(!db.objectStoreNames.contains('notes'))
notes = dbReq.transaction.objectStore('notes');
if (!notes.indexNames.includes('timestamp')) {
notes.createIndex('timestamp', 'timestamp');
}
notes.createIndex('title', 'title', {unique: true})
tx.objectStore('notes').index('timestamp').get(1533144673015)
let reverseOrder = false;
let tx = db.transaction(['notes'], 'readonly');
let store = tx.objectStore('notes');
// Retrieve the sticky notes index to run our cursor query on;
// the results will be ordered by their timestamp
let index = store.index('timestamp');
// Create our openCursor request; if we're going in reverse, then
// specify the direction as prev, otherwise, we specify it as next
let req = index.openCursor(null, reverseOrder ? 'prev' : 'next');
let anHourAgoInMS = Date.now() - 60 * 60 * 1000;// IDBKeyRange is a global variable for defining ranges to query
// indices on
let keyRange = IDBKeyRange.lowerBound(anHourAgoInMS)
let req = index.openCursor(keyRange, 'next')
reverseOrder ? 'prev' : 'next'
function flipNoteOrder(notes) {
reverseOrder = !reverseOrder;
getAndDisplayNotes(db);
}
<button onclick="flipNoteOrder()">Flip note order</button>
The final product!

STAY SLOTHFUL!

Andy Haskell

Written by

Gopher (Golang developer) + frontend = #SurfaceGopher! Warning: My blog posts may contain sloths!

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade