Storage
Web apps can use browser APIs to store unencrypted data locally on the user's computer. Client-side storage is separated by origin, such that pages from one site can't access data stored by pages from another site. However, two pages from the same site can share storage and use it as a communication mechanism. Data can be stored temporarily until the window closes, or permanently for years to come.
Web storage
The Web Storage API consists of the localStorage
and sessionStorage
properties of the Window object, which are Storage objects. A Storage object maps string keys to string values.
The localStorage
data is shared across open tabs in the same browser with the same document origin. The data stays persistent in the browser even after the user closes the browser's window. The sessionStorage
data is stored only for the duration of the session and is not shared across open tabs.
localStorage.firstname = "Roman";
localStorage.lastname = "Akchurin";
localStorage.jobtitle = "Developer";
You can use Object.keys()
to enumerate Storage properties.
Object.keys(localStorage); // => ['jobtitle', 'firstname', 'lastname']
The delete
operator removes properties from the Storage object, and the clear()
method removes all data from the Storage object.
delete localStorage.firstname;
Object.keys(localStorage); // => ['jobtitle', 'lastname']
// localStorage.clear();
Object.keys(localStorage); // => []
Storage objects define getItem()
, setItem()
, and removeItem()
methods that also allow you to access and modify Storage object properties.
sessionStorage.setItem("username", "romanak");
sessionStorage.getItem("username"); // => 'romanak'
sessionStorage.removeItem("username");
Whenever the data stored in localStorage
changes, the browser triggers a "storage" event on any Window objects to which that data is visible but not on the window that made the change.
window.onstorage = (event) => {
console.log(
`${event.url} ${event.key}: ${event.oldValue} => ${event.newValue}`
);
};
Cookies
Cookies are designed for use by server-side scripts for storing small amounts of data. Data stored as cookies is always transmitted between the web browser and the server with every HTTP request, so server-side scripts can read and write cookie values that are stored on the client.
You can access cookies through document.cookie
property that returns a string of all cookies that apply to the current document.
document.cookie; // => ''
To set a cookie, you assign a string to document.cookie
. This string should contain the cookie name, value, and optional attributes such as expires
, max-age
, path
, domain
, Secure
, and HttpOnly
.
document.cookie = "firstname=roman";
By default, a cookie lasts for the current document session, but its lifetime in seconds can be extended with expires
and max-age
attributes. The expires
attribute specifies an absolute date and time when the cookie should expire. You set it using a date string in a specific format.
let date = new Date();
date.setTime(date.getTime() + 100 * 1000); // 100 seconds from now
document.cookie = "firstname=roman; expires=" + date.toUTCString();
The max-age
attribute specifies the number of seconds from the current time after which the cookie should expire. It sets a relative expiration time. If both expires
and max-age
are set, the max-age
attribute takes precedence over expires
attribute.
document.cookie = "lastname=roman; max-age=100";
By default, if you don't specify the path attribute, the cookie is only accessible within the directory or subdirectory where it was created. This can lead to unexpected behavior if different parts of your website set and read cookies. By setting path=/
, you ensure that the cookie is accessible across the entire domain, which is often the desired behavior for session management, user preferences, and other common uses of cookies.
document.cookie = "firstname=roman; path=/";
If you want a cookie to be accessible on both cart.example.com
and shop.example.com
, you should set the domain
attribute to ".example.com". Note the leading dot, which is crucial for specifying that the cookie should be available to all subdomains of example.com. Cookies are associated with domains and paths, but not with specific ports. This means that cookies are sent based on the domain and path, regardless of the port number being used.
document.cookie = "jobtitle=developer; path=/; domain=.localhost";
The Secure
attribute in cookies is used to ensure that a cookie is only sent over secure, encrypted connections (HTTPS). When a cookie has the Secure
attribute, it tells the browser to only include the cookie in requests made over HTTPS, thereby enhancing the security of the data. You can combine the Secure
attribute with the HttpOnly
attribute for added security. The HttpOnly
attribute prevents client-side scripts from accessing the cookie, further reducing the risk of cross-site scripting (XSS) attacks.
document.cookie = "username=romanak; Secure; HttpOnly";
All cookies are returned in a single string, separated by a semicolon with space. You can process cookies to return a Map of cookie names and values.
function getCookies() {
let cookies = new Map();
let all = document.cookie;
let list = all.split("; ");
for (let cookie of list) {
if (!cookie.includes("=")) continue;
let p = cookie.indexOf("=");
let name = cookie.substring(0, p);
let value = cookie.substring(p + 1);
value = decodeURIComponent(value);
cookies.set(name, value);
}
return cookies;
}
You can write a convenience function to set cookie values that is best suited for your application.
function setCookie(name, value, days) {
if (days) {
expires = "; max-age=" + days * 60 * 60 * 24;
}
document.cookie =
name + "=" + encodeURIComponent(value) + expires + "; path=/";
}
You delete cookies by setting the max-age
property of the cookie to 0 or any negative number.
function removeCookie(name) {
document.cookie = name + "=; max-age=0; path=/";
}
IndexedDB
IndexedDB is an asynchronous API to object database with indexing. IndexedDB databases are scoped to the document origin such that two web pages with the same origin can access each other's data.
A database is a collection of named object stores. The objects are serialized into the object store with the structured clone algorithm, which means you can store properties with such values as Maps, Sets, and typed arrays. Each object must have a unique key by which it can be sorted and retrieved from the object store. In order to search based on the value of properties, you must define indexes on the object store.
To open a database, you make a request to indexedDB.open()
method, specifying the name of the database in the first argument and the version of the database as the second argument. This version number determines the database schema.
let db;
const request = indexedDB.open("MyDatabase", 1);
You can handle any errors of opening the database and save the opened database instance for later use. Because error events bubble, you may want to add a single error handler for all database requests.
request.onerror = (event) => {
console.error("Error opening IndexedDB:", event.target.errorCode);
};
request.onsuccess = (event) => {
db = event.target.result;
// Generic error hanler for all database request
db.onerror = (event) => {
console.error("IndexedDB error:", event.target.errorCode);
};
};
When you create a new database or increase the version number of an existing database, the "upgradeneeded" event is triggered. You should create the object stores needed for this version of the database in the handler for the "upgradeneeded" event. The createObjectStore()
method takes a name of the store and a parameter object and creates the object store.
request.onupgradeneeded = (event) => {
const db = event.target.result;
const store1 = db.createObjectStore("customers", { keyPath: "ssn" });
store1.createIndex("name", "name", { unique: false });
store1.createIndex("email", "email", { unique: true });
const store2 = db.createObjectStore("names", { autoIncrement: true });
console.log("Database schema created.");
};
To read data or make changes to the object store, you must start a transaction in one of the three modes: "readonly", "readwrite", or "versionchange". The first argument to transaction()
method is a list of object stores that transaction will span. You can run multiple "readonly" transactions with overlapping scopes, but only one "readwrite" transaction for an object store.