Make websites work offline - Offline Storage. Making IndexedDB the Hero!
Tags: webdev, database, html, javascript
Published: 2020-02-12 09:33:35

*Note: This article does not expect you to know anything from Part 1.*

Traditionally cookies were used for storing local data. But with HTML5 APIs, we got new options like `localStorage`, `sessionStorage`, `WebSQL`, and `IndexedDB`. In this article, we will specifically talk about IndexedDB.

Let's say you got Service Workers setup done and now your website loads offline. But... what if you want to store and retrieve a particular data? you cannot just `fetch()` from your API since the user is offline.

In this case, you can store data in IndexedDB!

> The Indexed Database API is a JavaScript application programming interface provided by web browsers for managing a NoSQL database of JSON objects. It is a standard maintained by the World Wide Web Consortium.
>
> <cite>~ [Wikipedia](https://en.wikipedia.org/wiki/Indexed_Database_API) </cite>

IndexedDB is provided by the browser and thus does not need internet for performing CRUD (Create Read Update Delete) operations. It is something like SQLite in Android (minus the SQL).

# Implementation
If you prefer learning yourself from codesandbox, you can checkout [IndexedDB Example](#example).



For the browsers that use prefix, we can start with something like
```js
window.indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction || {READ_WRITE: "readwrite"};
window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;

if (!window.indexedDB) {
	console.log("Your browser doesn't support a stable version of IndexedDB. Such and such feature will not be available.");
}
```
Before we go to the next code, let me warn you about something: IndexedDB is not [promisified](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) and thus is largely dependent on `onsuccess` and `onerror` callbacks. There are libraries like [idb](https://www.npmjs.com/package/idb) that provide promisified version of IndexedDB but for this article I will stick to the vanilla IndexedDB code. 


## Open/Create Database
*Opening a database automatically creates new database if it doesn't exist*
```js
let db;
const request = indexedDB.open("MyTestDatabase");
request.onsuccess = function(event) {
	db = event.target.result;
};

```

### > Defining schema/values
When you create a new database, the `onupgradeneeded` event will be triggered. We can create objectStores here,
```js
request.onupgradeneeded = function() {
	const db = event.target.result;
	const userObjectStore = db.createObjectStore("users", {keyPath: "userid"});
	userObjectStore.createIndex("name", "name", { unique: false });
	userObjectStore.createIndex("email", "email", { unique: true });
}
```

Thus, the complete code to create/open a database would look something like:
```js
async function openDatabase() {
	return new Promise((resolve, reject) => {
		const request = indexedDB.open("MyTestDatabase");
		request.onsuccess = function(event) {
			resolve(event.target.result);
		}

		request.onupgradeneeded = function() {
			const db = event.target.result;
			const userObjectStore = db.createObjectStore("users", {keyPath: "userid"});
			userObjectStore.createIndex("name", "name", { unique: false });
			userObjectStore.createIndex("email", "email", { unique: true });
		}		
	}) 
}

openDatabase()
	.then(db => {
		// db instance accessible here

	})
```

## Add data
Now we have `db` object accessible in `openDatabase()` promise. We can use this object to add/read/delete the data from IndexedDB.
```js
(async function() {
	const db = await openDatabase();

	// Add
	const userReadWriteTransaction = db.transaction("users", "readwrite");
	const newObjectStore = userReadWriteTransaction.objectStore("users");

	newObjectStore.add({
		userid: "4",
		name: "John Doe",
		email: "josn@gmail.com"
	});

	userReadWriteTransaction.onsuccess = function(e) {
		console.log("Data Added");
	}

})();
```

## Remove data

```js
const request = db.transaction("users", "readwrite")
	.objectStore("users")
	.delete("4");

request.onsuccess = function(event) {
	console.log("Deleted!");
};
```

## Read and Update Data
```js
const readTransaction = db.transaction(["users"]);
const objectStore = transaction.objectStore("customers");
const request = objectStore.get("4");

request.onsuccess = function(event) {
	console.log("User is " + request.result.name);
	const data = event.target.result;
	data.name = "John Doe";

	const updateRequest = objectStore.put(data);
	updateRequest.onsuccess = function(event) {
		console.log("Data Updated!");
	}
};
```

# Example
{% codesandbox indexeddb-example-trv2f %}

# Usecase? 

1. If you have an API that always (or most of the times) returns same values, you can call API, store the response in IndexedDB and when next time user calls the API, you can return it from IndexedDB right there and maybe later call API and store the updated value. 

2. I use IndexedDB in my application [PocketBook](https://pocketbook.cc) which is a Google Keep alternative where you can store your todos, goals, etc. PocketBook uses IndexedDB by default to store notebook's information. Thus you can use pocketbook even when you are offline!


----
***MDN Docs:*** https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Using_IndexedDB
***codesandbox example:*** https://codesandbox.io/s/indexeddb-example-trv2f
***PocketBook:*** https://pocketbook.cc

----
Thank you for reading! If you have any interesting project where you're using IndexedDB, do drop the link below!