DEV Community

Steve Alila
Steve Alila

Posted on • Edited on

3 Typical Ways to Store Data Using JavaScript

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:

const todosUl = document.querySelector('.todos');
const input = document.querySelector('#add');
const saveBtn = document.querySelector('#save');
Enter fullscreen mode Exit fullscreen mode

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

const todos = ['play piano', 'write some code', 'swim'];
Enter fullscreen mode Exit fullscreen mode

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

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);
})
Enter fullscreen mode Exit fullscreen mode

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.

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

Enter fullscreen mode Exit fullscreen mode

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:

localStorage.getItem('store_name');

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

Enter fullscreen mode Exit fullscreen mode

You can delete the storage object

localStorage.removeItem('store_name');

//e.g
localStorage.removeItem('name');
Enter fullscreen mode Exit fullscreen mode

Or clear all storage objects using

localStorage.clear();
Enter fullscreen mode Exit fullscreen mode

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

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

Enter fullscreen mode Exit fullscreen mode

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.

const saveTodos = inputData => {
    const todos = getTodos();
    todos.push(inputData);
    localStorage.setItem('todos', JSON.stringify(todos));
}
Enter fullscreen mode Exit fullscreen mode

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

  saveTodos(input.value);
Enter fullscreen mode Exit fullscreen mode

as shown below:

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

    let li = document.createElement('li');
    li.textContent =  input.value;
    saveTodos(input.value);
    todosUl.appendChild(li);
    input.value = '';
}
Enter fullscreen mode Exit fullscreen mode

Finally, we can delete each element in the array

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

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.

let db;
Enter fullscreen mode Exit fullscreen mode

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

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();
    })

}

Enter fullscreen mode Exit fullscreen mode

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()

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;
    })
}
Enter fullscreen mode Exit fullscreen mode

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 completed 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.

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()
        }
    })
}

Enter fullscreen mode Exit fullscreen mode

//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.

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)
    })
}

Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)