JavaScript Storage APIs - ryzendew/Linux-Tips-and-Tricks GitHub Wiki
JavaScript Storage APIs Guide
Table of Contents
Introduction
JavaScript provides several ways to store data in the browser. Each has different use cases and characteristics.
localStorage
localStorage stores data with no expiration time. Data persists even after closing the browser.
Basic Operations
// Set item
localStorage.setItem('username', 'Alice');
localStorage.setItem('preferences', JSON.stringify({ theme: 'dark', lang: 'en' }));
// Get item
const username = localStorage.getItem('username');
const prefs = JSON.parse(localStorage.getItem('preferences'));
// Remove item
localStorage.removeItem('username');
// Clear all
localStorage.clear();
// Check if key exists
localStorage.getItem('username') !== null; // true/false
Properties and Methods
// Length
console.log(localStorage.length); // Number of items
// Get key by index
console.log(localStorage.key(0)); // First key
// Iterate over all items
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
const value = localStorage.getItem(key);
console.log(key, value);
}
// Using forEach (modern)
Object.keys(localStorage).forEach(key => {
console.log(key, localStorage.getItem(key));
});
Storage Events
// Listen for changes in localStorage (on other tabs)
window.addEventListener('storage', (event) => {
console.log('Key changed:', event.key);
console.log('Old value:', event.oldValue);
console.log('New value:', event.newValue);
console.log('URL:', event.url);
});
Limitations
// Storage limits (approximately 5-10MB)
console.log('Approximate quota:', 5 * 1024 * 1024, 'bytes');
// Only stores strings
localStorage.setItem('number', 42); // Stored as "42"
localStorage.setItem('boolean', true); // Stored as "true"
localStorage.setItem('array', [1, 2, 3]); // Stored as "1,2,3"
// Solution: Use JSON
localStorage.setItem('data', JSON.stringify({
number: 42,
boolean: true,
array: [1, 2, 3]
}));
sessionStorage
sessionStorage stores data for one session (until the tab is closed).
Basic Operations
// Same API as localStorage
sessionStorage.setItem('tempData', 'value');
const value = sessionStorage.getItem('tempData');
sessionStorage.removeItem('tempData');
sessionStorage.clear();
// Data is cleared when tab/window closes
sessionStorage.setItem('sessionData', 'This will be gone when tab closes');
Use Cases
// Remember form progress during a session
function saveFormProgress(field, value) {
sessionStorage.setItem(`form_${field}`, value);
}
// Store sensitive data that shouldn't persist
sessionStorage.setItem('authToken', 'temporary-token');
// Temporary calculations
sessionStorage.setItem('calculation', JSON.stringify({ result: 42 }));
IndexedDB
IndexedDB is a powerful client-side database for storing large amounts of structured data.
Opening a Database
// Open database
const request = indexedDB.open('MyDatabase', 1);
// Handle success
request.onsuccess = (event) => {
const db = event.target.result;
console.log('Database opened');
};
// Handle errors
request.onerror = (event) => {
console.error('Database error:', event.target.error);
};
// Handle upgrades (schema changes)
request.onupgradeneeded = (event) => {
const db = event.target.result;
// Create object store
if (!db.objectStoreNames.contains('users')) {
const userStore = db.createObjectStore('users', { keyPath: 'id', autoIncrement: true });
// Create indexes
userStore.createIndex('name', 'name', { unique: false });
userStore.createIndex('email', 'email', { unique: true });
}
};
Adding Data
function addUser(user) {
const request = indexedDB.open('MyDatabase', 1);
request.onsuccess = (event) => {
const db = event.target.result;
const transaction = db.transaction(['users'], 'readwrite');
const store = transaction.objectStore('users');
const addRequest = store.add(user);
addRequest.onsuccess = () => {
console.log('User added:', addRequest.result);
};
};
}
addUser({ name: 'Alice', email: '[email protected]', age: 25 });
Reading Data
function getUser(id) {
return new Promise((resolve, reject) => {
const request = indexedDB.open('MyDatabase', 1);
request.onsuccess = (event) => {
const db = event.target.result;
const transaction = db.transaction(['users'], 'readonly');
const store = transaction.objectStore('users');
const getRequest = store.get(id);
getRequest.onsuccess = () => {
resolve(getRequest.result);
};
getRequest.onerror = () => {
reject(getRequest.error);
};
};
});
}
// Get all users
function getAllUsers() {
return new Promise((resolve, reject) => {
const request = indexedDB.open('MyDatabase', 1);
request.onsuccess = (event) => {
const db = event.target.result;
const transaction = db.transaction(['users'], 'readonly');
const store = transaction.objectStore('users');
const getAllRequest = store.getAll();
getAllRequest.onsuccess = () => {
resolve(getAllRequest.result);
};
};
});
}
Updating Data
function updateUser(id, updates) {
const request = indexedDB.open('MyDatabase', 1);
request.onsuccess = (event) => {
const db = event.target.result;
const transaction = db.transaction(['users'], 'readwrite');
const store = transaction.objectStore('users');
// First get the user
const getRequest = store.get(id);
getRequest.onsuccess = () => {
const user = getRequest.result;
const updatedUser = { ...user, ...updates };
store.put(updatedUser);
};
};
}
Deleting Data
function deleteUser(id) {
const request = indexedDB.open('MyDatabase', 1);
request.onsuccess = (event) => {
const db = event.target.result;
const transaction = db.transaction(['users'], 'readwrite');
const store = transaction.objectStore('users');
store.delete(id);
};
}
// Clear all users
function clearUsers() {
const request = indexedDB.open('MyDatabase', 1);
request.onsuccess = (event) => {
const db = event.target.result;
const transaction = db.transaction(['users'], 'readwrite');
const store = transaction.objectStore('users');
store.clear();
};
}
Using with Promises (Wrapper)
class IndexedDBWrapper {
constructor(dbName, version, stores) {
this.dbName = dbName;
this.version = version;
this.stores = stores;
}
open() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.dbName, this.version);
request.onupgradeneeded = (event) => {
const db = event.target.result;
this.stores.forEach(store => {
if (!db.objectStoreNames.contains(store.name)) {
const objectStore = db.createObjectStore(store.name, store.options);
store.indexes?.forEach(index => {
objectStore.createIndex(index.name, index.keyPath, index.options);
});
}
});
};
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
async get(storeName, key) {
const db = await this.open();
return new Promise((resolve, reject) => {
const transaction = db.transaction(storeName, 'readonly');
const store = transaction.objectStore(storeName);
const request = store.get(key);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
async getAll(storeName) {
const db = await this.open();
return new Promise((resolve, reject) => {
const transaction = db.transaction(storeName, 'readonly');
const store = transaction.objectStore(storeName);
const request = store.getAll();
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
async add(storeName, data) {
const db = await this.open();
return new Promise((resolve, reject) => {
const transaction = db.transaction(storeName, 'readwrite');
const store = transaction.objectStore(storeName);
const request = store.add(data);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
async put(storeName, data) {
const db = await this.open();
return new Promise((resolve, reject) => {
const transaction = db.transaction(storeName, 'readwrite');
const store = transaction.objectStore(storeName);
const request = store.put(data);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
async delete(storeName, key) {
const db = await this.open();
return new Promise((resolve, reject) => {
const transaction = db.transaction(storeName, 'readwrite');
const store = transaction.objectStore(storeName);
const request = store.delete(key);
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
}
}
Cookies
Cookies are small pieces of data sent between browser and server.
Setting Cookies
// Basic cookie
document.cookie = "username=Alice";
document.cookie = "theme=dark";
document.cookie = "session=abc123; max-age=3600"; // Expires in 1 hour
// With all options
document.cookie = "token=xyz789; " +
"expires=" + new Date(Date.now() + 86400000).toUTCString() + "; " +
"path=/; " +
"domain=example.com; " +
"secure; " +
"samesite=strict";
Reading Cookies
// Get all cookies
console.log(document.cookie);
// Parse cookies
function getCookie(name) {
const nameEQ = name + "=";
const cookies = document.cookie.split(';');
for (let cookie of cookies) {
cookie = cookie.trim();
if (cookie.indexOf(nameEQ) === 0) {
return cookie.substring(nameEQ.length);
}
}
return null;
}
console.log(getCookie('username'));
console.log(getCookie('theme'));
Deleting Cookies
// Delete by setting expiration to past
document.cookie = "username=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";
Comparison
| Feature | localStorage | sessionStorage | IndexedDB | Cookies |
|---|---|---|---|---|
| Capacity | ~5-10MB | ~5-10MB | Large (50MB+) | ~4KB |
| Data Type | Strings only | Strings only | Any | Strings |
| Sync/Async | Sync | Sync | Async | Sync |
| Expiration | Never | Tab close | Manual | Manual |
| Server Access | No | No | No | Yes |
| Performance | Fast | Fast | Slower | Slow |
Practical Examples
Theme Switcher with localStorage
class ThemeSwitcher {
constructor() {
this.theme = localStorage.getItem('theme') || 'light';
this.applyTheme();
}
toggle() {
this.theme = this.theme === 'light' ? 'dark' : 'light';
this.applyTheme();
localStorage.setItem('theme', this.theme);
}
applyTheme() {
document.body.classList.remove('light', 'dark');
document.body.classList.add(this.theme);
}
}
const themeSwitcher = new ThemeSwitcher();
Form Auto-Save with sessionStorage
class FormAutoSave {
constructor(formId, key) {
this.form = document.getElementById(formId);
this.key = key;
this.load();
this.setupListeners();
}
setupListeners() {
this.form.addEventListener('input', (e) => {
this.save();
});
}
save() {
const data = new FormData(this.form);
const obj = Object.fromEntries(data);
sessionStorage.setItem(this.key, JSON.stringify(obj));
}
load() {
const saved = sessionStorage.getItem(this.key);
if (saved) {
const data = JSON.parse(saved);
Object.entries(data).forEach(([key, value]) => {
const input = this.form.elements[key];
if (input) input.value = value;
});
}
}
clear() {
sessionStorage.removeItem(this.key);
}
}
Offline Data with IndexedDB
class OfflineStore {
constructor() {
this.dbName = 'OfflineApp';
this.dbVersion = 1;
}
async init() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.dbName, this.dbVersion);
request.onupgradeneeded = (event) => {
const db = event.target.result;
if (!db.objectStoreNames.contains('cache')) {
const cacheStore = db.createObjectStore('cache', { keyPath: 'url' });
cacheStore.createIndex('timestamp', 'timestamp', { unique: false });
}
};
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
async cacheResponse(url, response) {
const data = {
url,
response: await response.clone().json(),
timestamp: Date.now()
};
const db = await this.init();
const tx = db.transaction('cache', 'readwrite');
tx.objectStore('cache').put(data);
}
async getCached(url) {
const db = await this.init();
return new Promise((resolve) => {
const tx = db.transaction('cache', 'readonly');
const request = tx.objectStore('cache').get(url);
request.onsuccess = () => resolve(request.result);
});
}
}
User Preferences with localStorage
class UserPreferences {
constructor() {
this.defaults = {
theme: 'light',
language: 'en',
notifications: true,
fontSize: 'medium'
};
this.prefs = this.load();
}
load() {
const saved = localStorage.getItem('preferences');
return saved ? { ...this.defaults, ...JSON.parse(saved) } : this.defaults;
}
save() {
localStorage.setItem('preferences', JSON.stringify(this.prefs));
}
get(key) {
return this.prefs[key];
}
set(key, value) {
this.prefs[key] = value;
this.save();
this.applyPreference(key, value);
}
applyPreference(key, value) {
switch (key) {
case 'theme':
document.body.classList.toggle('dark', value === 'dark');
break;
case 'fontSize':
document.body.style.fontSize = { small: '14px', medium: '16px', large: '18px' }[value];
break;
}
}
}