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:
- Static storage
- LocalStorage
- SessionStorage
- IndexedDB
- WebSQL
- Cookies
- Cache API
In this guide, we will explore the three typical ways to store data using JavaScript. These are:
- Static storage
- Local storage
- 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
- 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
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');
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.clear();
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
saveTodos(input.value);
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 = '';
}
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
// 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;
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.
//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;
})
}
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()
}
})
}
//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)
})
}
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)