3 Typical Ways to Store Data Using JavaScript
In this guide, we will explore the three typical ways to store data using JavaScript. These are:
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
- You should understand ES6 JavaScript because we shall write the code in ES6 syntax.
- Get the complete to-do application code on GitHub: https://github.com/Alilasteve/todo
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 .
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.
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');
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'];
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); >)
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');
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
You can delete the storage object
localStorage.removeItem('store_name'); //e.g localStorage.removeItem('name');
Or clear all storage objects using
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; >
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)); >
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
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
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
First, we create a global instance of the database since we shall use it in few places in the following functions.
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(); >) >
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.
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 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.
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() > >) >
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) >) >
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.
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.