3 Typical Ways to Store Data Using JavaScript
Published: 2021-06-23 07:29:36

You have probably worked with an SQL database like MySQL. You created your site's frontend, then used a programming language to write the backend code.

You can also use JavaScript (Node.js.) to create a server to interact with a database like PostgreSQL or MongoDB.

The above setups can be long and tiring for a simple project.

The solution is to store data on the client, read, update, and delete it using JavaScript via:

1. Static storage
2. LocalStorage
3. SessionStorage
4. IndexedDB
5. WebSQL
6. Cookies
7. Cache API

In this guide, we will explore the three typical ways to store data using JavaScript. These are:

1. Static storage
2. Local storage
3. Using the browser database

We will learn the traits of each storage, how it works, and its disadvantages. To grasp the concepts, we will create a simple to-do application.

We shall not use any framework or library. Instead, we shall interact with DOM API, web Storage API, and `IndexedDB` API.

**Preliminaries**

1. You should understand ES6 JavaScript because we shall write the code in ES6 syntax.
2. Get the complete `to-do` application code on GitHub: https://github.com/Alilasteve/todo

**HTML**

We link the stylesheet and script file using the `defer` attribute. The defer keyword means our JavaScript will load after the HTML has finished loading. 

The body has a `form` for submitting `todos` for storage and `ul` for showing `todos`. 

**CSS**

We display the body's content at the center. Assign the `todos` some padding to give room for double-clicking when we start deleting them.

**JavaScript**

We have 3 JavaScript files: static.js for static storage, local.js for `locaStorage`, and db.js for `IndexedDB`. We shall switch their (HTML) script source depending on the storage we are working with.

Let's interact with the DOM.

We start by grabbing the `ul`,  `input`, and save `button`. And hold them in variables as shown below:

```javascript
const todosUl = document.querySelector('.todos');
const input = document.querySelector('#add');
const saveBtn = document.querySelector('#save');
```

## 1. Static Storage

This is the most basic way to store data using JavaScript. All we do is create a variable to hold the data. For example, we could create an array of `todos` such as 

```javascript
const todos = ['play piano', 'write some code', 'swim'];
```

We run the code inside the window object with `DOMContentLoaded` to ensure the `todos` are available to be shown when the page loads. 

```javascript
window.addEventListener('DOMContentLoaded', () =>{
        //show todos
    todos.forEach( todo => {
        let li = document.createElement('li');
        li.textContent =  todo;
        todosUl.appendChild(li);
        //delete todos
        li.addEventListener('dblclick', () => {
            todosUl.removeChild(li);
        })
    })

    const addTodos = e => {
    e.preventDefault();

    let li = document.createElement('li');
    li.textContent =  input.value;
    todos.push(input.value);
    todosUl.appendChild(li);
    input.value = '';
}
saveBtn.addEventListener('click', addTodos);
})
```

We loop through the `todos`, create a `li` element and show each todo on the page. We can add new `todos` to the array using the `addTodos` function. 

### Disadvantages 

We can add more `todos` to the static array, but they disappear on page refresh. That is why we need alternative ways to store the `todos`.

## 2.  LocalStorage

JavaScript allows us to store data in the browser using local storage API. Here, you can use `LocalStorage` and `SessionStorage`. The objects let us store data (in key/value pairs) and update it from the browser's storage. To view the data, open your browser. Right-click. Click Inspect => Application => Storage.

Both ways have the same structure. The main difference is that `LocalStorage` lets us store data as long as we want. `SessionStorage`, by contrast, loses the data when we close the current window. 

### How It Works

To store data use two parameters as shown.

```javascript
localStorage.setItem('store_name', 'data_name'); 
//e.g
localStorage.setItem('name', 'Ahmed');

```

To test the code, clear the console (ctrl + L) and paste the above code with any store name you prefer.

You can retrieve the stored data using `getItem` as shown:

```javascript
localStorage.getItem('store_name');

//e.g
localStorage.getItem('name'); //Ahmed
//or 
localStorage // all stores

```

You can delete the storage object

```javascript
localStorage.removeItem('store_name');

//e.g
localStorage.removeItem('name');
```

Or clear all storage objects using

```javascript
localStorage.clear();
```

`localStorage` stores data in strings. In our todo application, we use it as follows:

```javascript
const getTodos = () => {
    let todos;
    if(localStorage.getItem('todos') === null){
        todos = [];
    }else {
        todos = JSON.parse(localStorage.getItem('todos'));
    }
    return todos;
}

```

We first create a variable, `todos.` Next, check if the browser already has a storage object called `todos.` If it does not exist, we create an empty array. If it already exists, we can grab it in readiness to be used in other parts of the code.

We use `JSON.parse` to change the `todos` from JSON to regular JavaScript literals. We then return the object to be used in other parts of the code.

```javascript
const saveTodos = inputData => {
    const todos = getTodos();
    todos.push(inputData);
    localStorage.setItem('todos', JSON.stringify(todos));
}
```

create `saveTodos`, with `inputData` parameter. We push the new data in the web storage. And recreate the `localStorage` object. We `stringify` the `todos` before saving it because `localStorage` stores data in strings. We then proceed and get the input value from the `addTodos` function in the line

```javascript
  saveTodos(input.value);
```

as shown below:

```javascript
const addTodos = e => {
    e.preventDefault();

    let li = document.createElement('li');
    li.textContent =  input.value;
    saveTodos(input.value);
    todosUl.appendChild(li);
    input.value = '';
}
```

Finally, we can delete each element in the array

```javascript
const deleteTodos = (todos, e) => {
    const targetLi = todos.indexOf(e.target.textContent);
    todos.splice(targetLi, 1);
    localStorage.setItem('todos', JSON.stringify(todos));
}
```

Here, don't use `localStorage.removeItem()` but use the `array.splice` method because we are not sure the order we can decide to delete each element from the `todos` store.

We find the specific index of the `li` we want to remove from `localStorage` using the `array.indexOf()` method. Add splice it from the array. Then, recreate the `localStorage` object after the modification.

### Advantage

This time around, the data persists even if we close and reopen the browser. 

### Disadvantage

We cannot store complex data types using `localStorage`. That's where `indexedDB` comes in.

## 3. IndexedDB

With `IndexedDB`, we can store various data types in many forms. It is a free database in the browser. The storage is not limited to strings as with `localStorage`. 

Since it stores data asynchronously, we can use promises to interact with the database. We would need to download a library such as `idb` by Jake Archibald to use the promise API. In this simple demo, we shall use the `IndexedDB` API, which uses events instead of promises. 

`IndexedDB` works like a NoSQL database, such as MongoDB.

We create a `database` name. Add object `stores`. Object stores are like tables in MySQL or models in MongoDB.

The structure of the stores comes from the `index`. In MongoDB, we would refer to them as `schema`.

The database (object) has a method called `transaction` that enables us to perform CRUD in the `IndexedDB`. In the process, we use a `cursor` to loop through records in the object store.

To simplify the (seemingly) complex database we shall create four functions as follows

// connectIDB() 

// addTodos()

// getTodos()

// deleteTodos()

First, we create a global instance of the database since we shall use it in few places in the following functions. 

```javascript
let db;
```

Next, we connect to the `db` using `connectDB()` function.

```javascript
const connectIDB = () => {

    const request = window.indexedDB.open('todos_db', 1);

    request.addEventListener('upgradeneeded', e => {
        db = e.target.result;

        const objectStore = db.createObjectStore('todos_data', { keyPath: 'id', autoIncrement: true })
        objectStore.createIndex('content', 'content', { unique: false });
    })
    request.addEventListener('error', e => {
		console.log(e.target.errorCode);
    })
    request.addEventListener('success', () => {
        db = request.result;
        getTodos();
    })
    
}

```

Let's open version 1 of the `todos_db` database using `window.indexedDB.open()` method.

Here, we talk about versions because we can upgrade the database if we change its structure. Next, we run three events on the database instance.

First, we check if the database opened exists or create it using the `upgradeneeded` event. Or if we try to open the database with a higher version than the existing one. We create the name of the store. We have called it `todos_data`. You can call it anything you want.

Next, we define an object with keypath and `autoIncrement`. Here, the `keyPath` contains a unique id for each record in the database. We are using `autoIncrement` to let `IndexedDB` automatically increase it. Then, we tell `IndexedDB` the actual data (name) each record should contain.

We want to store content (from the form), as we shall define when saving the actual form data. The data is NOT unique because we can allow the database to save another record with the same name.

Secondly, if there is an error as we try to open the database, we run the error event and log the error on the console. Lastly, on success, we store the result property of the database in the `db` variable. We can then display the result using `getTodos()` function.

Before getting the `todos`, let's create grab them from the form and save them.

//addTodos()

```javascript
const addTodos = e => {

    e.preventDefault();

    const transaction = db.transaction(['todos_data'], 'readwrite');
    const objectStore = transaction.objectStore('todos_data');
    const newRecord = {content: input.value};
    const request = objectStore.add(newRecord);

    request.addEventListener('success', () => {
        input.value = '';
    })
    transaction.addEventListener('complete', () => {
        getTodos();
    })
    transaction.addEventListener('error', () => {
        return;
    })
}
```

First, we prevent the form from its default behavior of refreshing the page for each submit using `e.preventDefault()`. 

Next, we create a `readwrite` transaction in our formerly created `todos_data` store. Next, we create a new create record and store it in the store.

If the request is successful, we clear the form in readiness for the following form input. Once the transaction has been **complete**d in the database, we can read the data.

If we fail to complete the transaction, we run an error event and stop further code execution. 

//getTodos()

We run a while loop to remove the existing `firstChild` of `ul` to avoid record duplication. Next, we get the target store. 

Using the `cursor` method and success event, create a `li` for every record using its unique id that we had saved earlier. 

From here, we can decide to delete the list using the delete function tied to each `li`. Remember to end the `cursor` with `cursor.continue()` to enable the code to proceed to the following `li` creation and deletion.

```javascript
const getTodos = () => {

    while(todosUl.firstChild){
        todosUl.removeChild(todosUl.firstChild)
    }
    const objectStore = db.transaction('todos_data').objectStore('todos_data');

    objectStore.openCursor().addEventListener('success', e => {
        const cursor = e.target.result

        if(cursor){
            const list = document.createElement('li')
            list.setAttribute('todo-id', cursor.value.id)
            list.textContent = cursor.value.content
            todosUl.appendChild(list)

            list.ondblclick = deleteTodos

            cursor.continue()
        }
    })
}

```

//deleteTodos()

Like `getTodos()`, we start a `readwrite` transaction on the object store. Convert each `todo`'s id to a number and find its match with the one stored in the database. Then, delete it. 

```javascript
const deleteTodos = e => {

    const transaction = db.transaction(['todos_data'], 'readwrite')
    const objectStore = transaction.objectStore('todos_data')
    const todoId = Number(e.target.getAttribute('todo-id'))
    objectStore.delete(todoId)

    transaction.addEventListener('complete', () => {
        if(!todosUl.firstChild){
            const message = document.createElement('li')
            message.textContent = 'No todo exist'
            todosUl.appendChild(message)
        }
        e.target.parentNode.removeChild(e.target)
    })
}

```

And voila! We have made a simple update to `IndexedDB`. 

Remember, you can store images, videos, and photos in `IndexedDB` as you would do in a typical server-side environment.

### Disadvantage

The main weakness of `IndexedDB` is that it can be complicated to use. 

**Conclusion**

This article aims to give you an overview of ways you can use JavaScript to store data on the client side. You can read more about each method and apply it according to your need.