Web Storage APIs (localStorage, sessionStorage, IndexedDB)
1. localStorage and sessionStorage Implementation
| API | Method | Description | Persistence |
|---|---|---|---|
| localStorage | localStorage.setItem(key, value) |
Store data persistently | Until explicitly deleted |
| sessionStorage | sessionStorage.setItem(key, value) |
Store data for session | Until tab/window closed |
| getItem | storage.getItem(key) |
Retrieve stored value | Returns string or null |
| removeItem | storage.removeItem(key) |
Delete specific item | - |
| clear | storage.clear() |
Delete all items | - |
| key | storage.key(index) |
Get key by index | Returns key name or null |
| length | storage.length |
Number of stored items | Read-only property |
Example: localStorage polyfill using cookies
// localStorage polyfill for older browsers (IE7 and below)
(function() {
if ('localStorage' in window && window.localStorage !== null) {
return;
}
var Storage = function() {
this.length = 0;
this._data = {};
};
Storage.prototype.setItem = function(key, value) {
if (!(key in this._data)) {
this.length++;
}
this._data[key] = String(value);
this._save();
};
Storage.prototype.getItem = function(key) {
return this._data.hasOwnProperty(key) ? this._data[key] : null;
};
Storage.prototype.removeItem = function(key) {
if (key in this._data) {
delete this._data[key];
this.length--;
this._save();
}
};
Storage.prototype.clear = function() {
this._data = {};
this.length = 0;
this._save();
};
Storage.prototype.key = function(index) {
var keys = Object.keys(this._data);
return index >= 0 && index < keys.length ? keys[index] : null;
};
Storage.prototype._save = function() {
// Save to cookie (with size limits)
var serialized = JSON.stringify(this._data);
document.cookie = this._cookieName + '=' +
encodeURIComponent(serialized) +
'; expires=Fri, 31 Dec 9999 23:59:59 GMT; path=/';
};
Storage.prototype._load = function() {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = cookies[i].trim();
if (cookie.indexOf(this._cookieName + '=') === 0) {
var value = cookie.substring(this._cookieName.length + 1);
try {
this._data = JSON.parse(decodeURIComponent(value));
this.length = Object.keys(this._data).length;
} catch(e) {}
break;
}
}
};
// Create localStorage
var localStorage = new Storage();
localStorage._cookieName = '__localStorage__';
localStorage._load();
// Create sessionStorage
var sessionStorage = new Storage();
sessionStorage._cookieName = '__sessionStorage__';
sessionStorage._load();
// Make read-only
try {
Object.defineProperty(window, 'localStorage', {
value: localStorage,
writable: false
});
Object.defineProperty(window, 'sessionStorage', {
value: sessionStorage,
writable: false
});
} catch(e) {
window.localStorage = localStorage;
window.sessionStorage = sessionStorage;
}
})();
Example: Safe storage access with try-catch
// Safe storage wrapper (handles private mode)
var SafeStorage = {
isAvailable: function(storageType) {
try {
var storage = window[storageType];
var test = '__storage_test__';
storage.setItem(test, test);
storage.removeItem(test);
return true;
} catch(e) {
return false;
}
},
setItem: function(key, value, storageType) {
storageType = storageType || 'localStorage';
try {
window[storageType].setItem(key, value);
return true;
} catch(e) {
console.warn('Storage unavailable:', e);
return false;
}
},
getItem: function(key, storageType) {
storageType = storageType || 'localStorage';
try {
return window[storageType].getItem(key);
} catch(e) {
console.warn('Storage unavailable:', e);
return null;
}
},
removeItem: function(key, storageType) {
storageType = storageType || 'localStorage';
try {
window[storageType].removeItem(key);
return true;
} catch(e) {
console.warn('Storage unavailable:', e);
return false;
}
}
};
// Usage
if (SafeStorage.isAvailable('localStorage')) {
SafeStorage.setItem('user', JSON.stringify({ name: 'John' }));
var user = JSON.parse(SafeStorage.getItem('user'));
}
Warning: localStorage may throw SecurityError in private
browsing mode or when storage quota is exceeded. Always wrap storage operations in
try-catch
blocks.
2. IndexedDB API Compatibility and Polyfills
| Feature | API | Description | Support |
|---|---|---|---|
| Database | indexedDB.open(name, version) |
Open/create database | IE10+ |
| Object Store | db.createObjectStore(name, options) |
Create table equivalent | In onupgradeneeded callback |
| Transaction | db.transaction(stores, mode) |
Create transaction scope | Modes: readonly, readwrite |
| CRUD Operations | store.add/get/put/delete() |
Data manipulation | Returns IDBRequest |
| Cursors | store.openCursor() |
Iterate over records | Event-driven iteration |
| Vendor Prefixes | webkitIndexedDB, mozIndexedDB |
Legacy browser support | Prefix normalization needed |
Example: IndexedDB vendor prefix normalization
// Normalize IndexedDB across browsers
window.indexedDB = window.indexedDB ||
window.mozIndexedDB ||
window.webkitIndexedDB ||
window.msIndexedDB;
window.IDBTransaction = window.IDBTransaction ||
window.webkitIDBTransaction ||
window.msIDBTransaction;
window.IDBKeyRange = window.IDBKeyRange ||
window.webkitIDBKeyRange ||
window.msIDBKeyRange;
// Check if IndexedDB is available
if (!window.indexedDB) {
console.warn('IndexedDB not supported');
}
// Promise wrapper for IndexedDB
var IDB = {
open: function(name, version) {
return new Promise(function(resolve, reject) {
var request = indexedDB.open(name, version);
request.onerror = function() {
reject(request.error);
};
request.onsuccess = function() {
resolve(request.result);
};
request.onupgradeneeded = function(event) {
var db = event.target.result;
// Handle upgrades
if (!db.objectStoreNames.contains('store')) {
db.createObjectStore('store', { keyPath: 'id', autoIncrement: true });
}
};
});
},
add: function(db, storeName, data) {
return new Promise(function(resolve, reject) {
var transaction = db.transaction([storeName], 'readwrite');
var store = transaction.objectStore(storeName);
var request = store.add(data);
request.onsuccess = function() {
resolve(request.result);
};
request.onerror = function() {
reject(request.error);
};
});
},
get: function(db, storeName, key) {
return new Promise(function(resolve, reject) {
var transaction = db.transaction([storeName], 'readonly');
var store = transaction.objectStore(storeName);
var request = store.get(key);
request.onsuccess = function() {
resolve(request.result);
};
request.onerror = function() {
reject(request.error);
};
});
}
};
// Usage
IDB.open('myDatabase', 1)
.then(function(db) {
return IDB.add(db, 'store', { name: 'John', age: 30 });
})
.then(function(id) {
console.log('Added with ID:', id);
})
.catch(function(error) {
console.error('IndexedDB error:', error);
});
Note: IndexedDB is asynchronous and event-driven. Use Promise
wrappers or libraries like
idb or localForage for easier API.
3. Cookie API and document.cookie Extensions
| Operation | Method | Options | Notes |
|---|---|---|---|
| Set Cookie | document.cookie = 'key=value' |
expires, path, domain, secure, samesite | Append, not replace |
| Get Cookie | Parse document.cookie |
- | Returns all cookies as string |
| Delete Cookie | Set with past expiration | expires=Thu, 01 Jan 1970 |
Must match path/domain |
| Size Limit | ~4KB per cookie | ~20-50 cookies per domain | Browser dependent |
| HttpOnly | Server-side only flag | Cannot access via JavaScript | Security feature |
| SameSite | SameSite=Strict|Lax|None |
CSRF protection | Modern browsers |
Example: Cookie utility library
// Cookie utility functions
var Cookies = {
set: function(name, value, options) {
options = options || {};
var cookieString = encodeURIComponent(name) + '=' + encodeURIComponent(value);
// Expires
if (options.expires) {
var expires;
if (typeof options.expires === 'number') {
expires = new Date();
expires.setTime(expires.getTime() + options.expires * 24 * 60 * 60 * 1000);
} else {
expires = options.expires;
}
cookieString += '; expires=' + expires.toUTCString();
}
// Path
cookieString += '; path=' + (options.path || '/');
// Domain
if (options.domain) {
cookieString += '; domain=' + options.domain;
}
// Secure
if (options.secure) {
cookieString += '; secure';
}
// SameSite
if (options.sameSite) {
cookieString += '; samesite=' + options.sameSite;
}
document.cookie = cookieString;
},
get: function(name) {
var nameEQ = encodeURIComponent(name) + '=';
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = cookies[i].trim();
if (cookie.indexOf(nameEQ) === 0) {
return decodeURIComponent(cookie.substring(nameEQ.length));
}
}
return null;
},
remove: function(name, options) {
options = options || {};
options.expires = -1;
this.set(name, '', options);
},
getAll: function() {
var cookies = {};
var cookieArray = document.cookie.split(';');
for (var i = 0; i < cookieArray.length; i++) {
var cookie = cookieArray[i].trim();
var parts = cookie.split('=');
if (parts.length === 2) {
cookies[decodeURIComponent(parts[0])] = decodeURIComponent(parts[1]);
}
}
return cookies;
},
has: function(name) {
return this.get(name) !== null;
}
};
// Usage
Cookies.set('username', 'john_doe', {
expires: 7, // 7 days
path: '/',
secure: true,
sameSite: 'Lax'
});
var username = Cookies.get('username');
console.log(username); // 'john_doe'
Cookies.remove('username');
var allCookies = Cookies.getAll();
console.log(allCookies);
Example: Storage wrapper using cookies as fallback
// Universal storage with cookie fallback
var UniversalStorage = {
_useLocalStorage: (function() {
try {
var test = '__test__';
localStorage.setItem(test, test);
localStorage.removeItem(test);
return true;
} catch(e) {
return false;
}
})(),
setItem: function(key, value, days) {
if (this._useLocalStorage) {
try {
localStorage.setItem(key, value);
return;
} catch(e) {}
}
// Fallback to cookies
Cookies.set(key, value, { expires: days || 365 });
},
getItem: function(key) {
if (this._useLocalStorage) {
try {
return localStorage.getItem(key);
} catch(e) {}
}
// Fallback to cookies
return Cookies.get(key);
},
removeItem: function(key) {
if (this._useLocalStorage) {
try {
localStorage.removeItem(key);
return;
} catch(e) {}
}
// Fallback to cookies
Cookies.remove(key);
}
};
// Usage - automatically uses localStorage or cookies
UniversalStorage.setItem('token', 'abc123');
var token = UniversalStorage.getItem('token');
Warning: Cookies have ~4KB size limit per cookie and are sent
with every HTTP request. Use localStorage for larger data. Never store sensitive data in cookies without
Secure and HttpOnly flags.
4. WebSQL to IndexedDB Migration Patterns
| Feature | WebSQL | IndexedDB | Status |
|---|---|---|---|
| Standard | W3C Working Draft | W3C Recommendation | WEBSQL DEPRECATED |
| API Style | SQL queries | NoSQL key-value | Different paradigms |
| Browser Support | Chrome, Safari (removed in new versions) | All modern browsers | Migrate to IndexedDB |
| Storage Limit | ~5-10MB default | ~50MB+ (quota API) | IndexedDB more flexible |
| Transactions | SQL transactions | Object store transactions | Both support ACID |
Example: WebSQL migration helper
// WebSQL migration utility
var WebSQLMigration = {
hasWebSQL: function() {
return 'openDatabase' in window;
},
hasIndexedDB: function() {
return 'indexedDB' in window;
},
migrateFromWebSQL: function(webSQLDB, indexedDBName, tableName, storeName) {
return new Promise(function(resolve, reject) {
if (!this.hasWebSQL() || !this.hasIndexedDB()) {
reject(new Error('Required APIs not available'));
return;
}
// Read from WebSQL
webSQLDB.transaction(function(tx) {
tx.executeSql('SELECT * FROM ' + tableName, [], function(tx, results) {
var rows = [];
for (var i = 0; i < results.rows.length; i++) {
rows.push(results.rows.item(i));
}
// Write to IndexedDB
var request = indexedDB.open(indexedDBName, 1);
request.onupgradeneeded = function(e) {
var db = e.target.result;
if (!db.objectStoreNames.contains(storeName)) {
db.createObjectStore(storeName, { keyPath: 'id' });
}
};
request.onsuccess = function(e) {
var db = e.target.result;
var transaction = db.transaction([storeName], 'readwrite');
var store = transaction.objectStore(storeName);
rows.forEach(function(row) {
store.add(row);
});
transaction.oncomplete = function() {
resolve(rows.length);
};
transaction.onerror = function() {
reject(transaction.error);
};
};
request.onerror = function() {
reject(request.error);
};
});
});
}.bind(this));
}
};
// Usage
var db = openDatabase('mydb', '1.0', 'My Database', 2 * 1024 * 1024);
WebSQLMigration.migrateFromWebSQL(db, 'myIndexedDB', 'users', 'users')
.then(function(count) {
console.log('Migrated ' + count + ' records');
})
.catch(function(error) {
console.error('Migration failed:', error);
});
Note: WebSQL is deprecated and removed from modern browsers.
Migrate existing WebSQL data to IndexedDB or use libraries like
localForage that abstract storage.
5. Memory-based Storage Fallback for Private Mode
| Strategy | Persistence | Use Case | Limitations |
|---|---|---|---|
| In-Memory Object | Session only (cleared on reload) | Last resort fallback | No persistence, lost on refresh |
| sessionStorage | Tab/window session | Temporary data for session | Lost when tab closed |
| localStorage | Persistent across sessions | Primary storage method | May fail in private mode |
| Cookie | Persistent (with expiration) | Fallback for localStorage | 4KB limit, sent with requests |
| IndexedDB | Persistent, large storage | Complex data, large datasets | Async API complexity |
Example: Complete storage fallback implementation
// Storage facade with fallback chain
var StorageFacade = (function() {
'use strict';
var storageType = null;
var memoryStorage = {};
// Test storage availability
function testStorage(type) {
try {
var storage = window[type];
var test = '__storage_test__';
storage.setItem(test, test);
storage.removeItem(test);
return true;
} catch(e) {
return false;
}
}
// Determine best available storage
function init() {
if (testStorage('localStorage')) {
storageType = 'localStorage';
} else if (testStorage('sessionStorage')) {
storageType = 'sessionStorage';
} else if ('cookie' in document) {
storageType = 'cookie';
} else {
storageType = 'memory';
}
console.log('Using storage type:', storageType);
}
function setItem(key, value) {
switch(storageType) {
case 'localStorage':
case 'sessionStorage':
try {
window[storageType].setItem(key, value);
} catch(e) {
console.warn('Storage quota exceeded, falling back to memory');
memoryStorage[key] = value;
}
break;
case 'cookie':
Cookies.set(key, value, { expires: 365 });
break;
case 'memory':
default:
memoryStorage[key] = value;
break;
}
}
function getItem(key) {
switch(storageType) {
case 'localStorage':
case 'sessionStorage':
try {
return window[storageType].getItem(key);
} catch(e) {
return memoryStorage[key] || null;
}
case 'cookie':
return Cookies.get(key);
case 'memory':
default:
return memoryStorage[key] || null;
}
}
function removeItem(key) {
switch(storageType) {
case 'localStorage':
case 'sessionStorage':
try {
window[storageType].removeItem(key);
} catch(e) {
delete memoryStorage[key];
}
break;
case 'cookie':
Cookies.remove(key);
break;
case 'memory':
default:
delete memoryStorage[key];
break;
}
}
function clear() {
switch(storageType) {
case 'localStorage':
case 'sessionStorage':
try {
window[storageType].clear();
} catch(e) {
memoryStorage = {};
}
break;
case 'cookie':
var cookies = Cookies.getAll();
Object.keys(cookies).forEach(function(key) {
Cookies.remove(key);
});
break;
case 'memory':
default:
memoryStorage = {};
break;
}
}
function getStorageType() {
return storageType;
}
// Initialize on load
init();
// Public API
return {
setItem: setItem,
getItem: getItem,
removeItem: removeItem,
clear: clear,
getStorageType: getStorageType
};
})();
// Usage - automatically uses best available storage
StorageFacade.setItem('user', JSON.stringify({ name: 'John', age: 30 }));
var user = JSON.parse(StorageFacade.getItem('user'));
console.log('Using storage:', StorageFacade.getStorageType());
Example: Storage with JSON serialization helper
// Enhanced storage with automatic JSON handling
var SmartStorage = {
set: function(key, value, options) {
var serialized = typeof value === 'string' ? value : JSON.stringify(value);
try {
StorageFacade.setItem(key, serialized);
return true;
} catch(e) {
console.error('Storage error:', e);
return false;
}
},
get: function(key, defaultValue) {
var value = StorageFacade.getItem(key);
if (value === null) {
return defaultValue !== undefined ? defaultValue : null;
}
try {
return JSON.parse(value);
} catch(e) {
// Return as string if not JSON
return value;
}
},
remove: function(key) {
StorageFacade.removeItem(key);
},
clear: function() {
StorageFacade.clear();
},
has: function(key) {
return StorageFacade.getItem(key) !== null;
}
};
// Usage with automatic JSON handling
SmartStorage.set('user', { name: 'John', age: 30, roles: ['admin', 'user'] });
var user = SmartStorage.get('user');
console.log(user.name); // 'John'
console.log(user.roles); // ['admin', 'user']
SmartStorage.set('counter', 42);
var counter = SmartStorage.get('counter');
console.log(typeof counter); // 'number'
var nonExistent = SmartStorage.get('missing', { default: 'value' });
console.log(nonExistent); // { default: 'value' }
Key Takeaways - Storage & Data Management
- localStorage/sessionStorage: Always wrap in try-catch for private mode handling
- IndexedDB: Normalize vendor prefixes, use Promise wrappers for easier API
- Cookies: 4KB limit, use for fallback or when persistence required
- WebSQL: Deprecated - migrate to IndexedDB immediately
- Fallback strategy: localStorage → sessionStorage → cookies → memory
- Libraries: Consider localForage or similar for abstraction