Web Storage API
The Web Storage API provides a way to store data in the user's browser: localStorage (persistent, doesn't disappear when the tab is closed) and sessionStorage (gone when the tab/session is closed). Both store data as string key-value pairs.
localStorage vs sessionStorage
| Feature | localStorage | sessionStorage |
|---|---|---|
| Duration | Permanent | Until the tab is closed |
| Scope | All tabs (same origin) | Active tab only |
| Capacity | ~5-10 MB | ~5-10 MB |
| Access | Sync API | Sync API |
Code Example
Basics — setItem, getItem, removeItem
// Store data
localStorage.setItem('nameUser', 'Mike Johnson');
localStorage.setItem('studentIdUser', '2024001');
// Retrieve data
const name = localStorage.getItem('nameUser');
console.log(name); // "Mike Johnson"
// Remove a single item
localStorage.removeItem('studentIdUser');
// Remove all items
localStorage.clear();
// Number of items
console.log(localStorage.length);
// Get a key by index
const key = localStorage.key(0);
Storing Objects/Arrays with JSON
// localStorage only stores strings
// So objects/arrays must be JSON.stringify'd first
const student = {
studentId: '2024001',
name: 'Mike Johnson',
major: 'Computer Science',
course: ['Algorithms', 'Database', 'Networks']
};
// Save
localStorage.setItem('student', JSON.stringify(student));
// Retrieve
const data = JSON.parse(localStorage.getItem('student'));
console.log(data.name); // "Mike Johnson"
console.log(data.course[0]); // "Algorithms"
// Safer helper functions
function saveToLocal(key, value) {
try {
localStorage.setItem(key, JSON.stringify(value));
return true;
} catch (error) {
console.error('Failed to save:', error);
return false;
}
}
function getFromLocal(key, defaultValue = null) {
try {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : defaultValue;
} catch (error) {
console.error('Read failed:', error);
return defaultValue;
}
}
Code Explanation
Storage Events
The storage event fires when a localStorage item is changed — but ONLY from another tab/browser (not the same tab).
// Listen for changes from other tabs
window.addEventListener('storage', function (event) {
console.log('Key changed:', event.key);
console.log('Grade old:', event.oldValue);
console.log('Grade new:', event.newValue);
console.log('URL page:', event.url);
});
// Useful for synchronization between tabs
// Example: user logs in on tab A → tab B updates automatically
window.addEventListener('storage', function (event) {
if (event.key === 'authToken' && event.newValue === null) {
// Token was removed from another tab — log out here too
window.location.href = '/login';
}
});
Error Handling
// localStorage can fill up — always handle the error
function safeSetItem(key, value) {
try {
localStorage.setItem(key, JSON.stringify(value));
return true;
} catch (error) {
if (error.name === 'QuotaExceededError') {
console.error('Storage isFull!');
// Clean up old data or show a warning
clearOldStorage();
} else {
console.error('Storage error:', error);
}
return false;
}
}
function clearOldStorage() {
// Remove non-critical data
const keysToClean = ['cache', 'draftForm', 'recentSearches'];
keysToClean.forEach(key => localStorage.removeItem(key));
}
Real-World Examples
Saving a Student Form Draft
const formStudent = document.querySelector('#form-student');
const STORAGE_KEY = 'draftStudent';
// Auto-save while typing
formStudent.querySelectorAll('input, select, textarea').forEach(field => {
field.addEventListener('input', function () {
saveDraft();
});
});
// Save the draft to localStorage
function saveDraft() {
const formData = new FormData(formStudent);
const draft = Object.fromEntries(formData);
draft.lastSaved = new Date().toISOString();
localStorage.setItem(STORAGE_KEY, JSON.stringify(draft));
}
// Load the draft when the page opens
function loadDraft() {
const saved = localStorage.getItem(STORAGE_KEY);
if (!saved) return;
const draft = JSON.parse(saved);
const lastSaved = new Date(draft.lastSaved).toLocaleString('id-ID');
const restore = confirm(
`Found a draft from ${lastSaved}.\nUse this draft?`
);
if (restore) {
Object.keys(draft).forEach(key => {
if (key === 'lastSaved') return;
const field = formStudent.querySelector(`[name="${key}"]`);
if (field) field.value = draft[key];
});
} else {
localStorage.removeItem(STORAGE_KEY);
}
}
// Remove the draft after a successful submit
formStudent.addEventListener('submit', async function (e) {
e.preventDefault();
try {
await submitForm(this);
localStorage.removeItem(STORAGE_KEY);
showNotification('Data tersave!', 'success');
} catch (error) {
console.error(error);
}
});
// Load the draft when the page is ready
loadDraft();
Remembering User Preferences
class UserPreferences {
constructor(storageKey = 'userPrefs') {
this.storageKey = storageKey;
this.defaults = {
theme: 'light',
language: 'id',
fontSize: '14',
sidebarOpen: true,
notificationsEnabled: true
};
}
get(key) {
const prefs = this.getAll();
return prefs[key] !== undefined ? prefs[key] : this.defaults[key];
}
set(key, value) {
const prefs = this.getAll();
prefs[key] = value;
try {
localStorage.setItem(this.storageKey, JSON.stringify(prefs));
this.applyPreferences();
} catch (error) {
console.error('Failed to save preferences:', error);
}
}
getAll() {
try {
const stored = localStorage.getItem(this.storageKey);
return stored ? JSON.parse(stored) : { ...this.defaults };
} catch {
return { ...this.defaults };
}
}
reset() {
localStorage.removeItem(this.storageKey);
this.applyPreferences();
}
applyPreferences() {
const prefs = this.getAll();
document.documentElement.setAttribute('data-theme', prefs.theme);
document.documentElement.style.fontSize = prefs.fontSize + 'px';
document.documentElement.lang = prefs.language;
}
}
// Initialize
const prefs = new UserPreferences();
prefs.applyPreferences(); // apply when the page loads
// Toggle theme
document.querySelector('#theme-toggle').addEventListener('click', function () {
const current = prefs.get('theme');
prefs.set('theme', current === 'light' ? 'dark' : 'light');
});
// Save font size
document.querySelector('#font-size').addEventListener('change', function () {
prefs.set('fontSize', this.value);
});
Saving Search History
class SearchHistory {
constructor(maxItems = 10) {
this.storageKey = 'searchHistory';
this.maxItems = maxItems;
}
add(query) {
let history = this.get();
// Remove duplicates
history = history.filter(item => item.query !== query);
// Add to the front
history.unshift({
query,
timestamp: new Date().toISOString()
});
// Limit the count
history = history.slice(0, this.maxItems);
localStorage.setItem(this.storageKey, JSON.stringify(history));
}
get() {
try {
return JSON.parse(localStorage.getItem(this.storageKey)) || [];
} catch {
return [];
}
}
clear() {
localStorage.removeItem(this.storageKey);
}
}
// Usage
const searchHistory = new SearchHistory();
// Search input
searchInput.addEventListener('keyup', function (e) {
if (e.key === 'Enter' && this.value.trim()) {
searchHistory.add(this.value.trim());
performSearch(this.value.trim());
}
});
// Show the history when the input is focused
searchInput.addEventListener('focus', function () {
const history = searchHistory.get();
if (history.length === 0) return;
const dropdown = document.createElement('div');
dropdown.className = 'search-dropdown';
dropdown.innerHTML = history.map(item => `
<div class="search-item" data-query="${item.query}">
${item.query}
</div>
`).join('');
searchInput.parentNode.appendChild(dropdown);
});
sessionStorage — Example
// SessionStorage — gone when the tab is closed
// Suited for temporary data within a single session
sessionStorage.setItem('currentStep', '1');
sessionStorage.getItem('currentStep'); // "1"
// Multi-step form — save progress per step
function saveCurrentStep(step, data) {
sessionStorage.setItem(`step${step}`, JSON.stringify(data));
}
function getStepData(step) {
return JSON.parse(sessionStorage.getItem(`step${step}`) || 'null');
}
// Student wizard form
const currentStep = parseInt(sessionStorage.getItem('currentStep') || '1');
// Step 1: Personal Data → Step 2: Choose Major → Step 3: Confirm
if (currentStep > 1) {
const prevData = getStepData(currentStep - 1);
if (!prevData) {
// No data from the previous step — go back to step 1
window.location.href = '?step=1';
}
}
Conclusion
The Web Storage API stores data in the browser without needing a server. Use localStorage for persistent data (preferences, drafts), and sessionStorage for session data (form progress, wizard steps). Always use try/catch to handle QuotaExceededError, and helper functions like saveToLocal/getFromLocal for safer code. Use JSON.stringify/parse to store objects or arrays.