Skip to main content

Module 15: Browser APIs (Storage, Fetch, etc.)

Modern browsers provide powerful Web APIs that enable rich functionality: data persistence, network requests, geolocation, notifications, and more.


1. Web Storage API

1.1 localStorage

// Set item (stores as string)
localStorage.setItem('username', 'john_doe');
localStorage.setItem('theme', 'dark');

// Get item
const username = localStorage.getItem('username');
console.log(username); // "john_doe"

// Remove item
localStorage.removeItem('theme');

// Clear all
localStorage.clear();

// Get number of items
console.log(localStorage.length);

// Get key by index
const firstKey = localStorage.key(0);

// Direct property access (not recommended)
localStorage.username = 'jane_doe';
console.log(localStorage.username);

1.2 sessionStorage

// Same API as localStorage, but data cleared when tab closes
sessionStorage.setItem('tempData', 'value');
const data = sessionStorage.getItem('tempData');
sessionStorage.removeItem('tempData');
sessionStorage.clear();

1.3 Storing Objects

// Must serialize to JSON
const user = {
name: 'John Doe',
age: 30,
preferences: { theme: 'dark' }
};

// Store
localStorage.setItem('user', JSON.stringify(user));

// Retrieve
const stored = localStorage.getItem('user');
const parsed = JSON.parse(stored);
console.log(parsed); // { name: 'John Doe', age: 30, ... }

// Helper functions
function setLocalStorage(key, value) {
localStorage.setItem(key, JSON.stringify(value));
}

function getLocalStorage(key, defaultValue = null) {
try {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : defaultValue;
} catch (error) {
console.error('Error reading from localStorage:', error);
return defaultValue;
}
}

1.4 Storage Events

// Listen for storage changes (across tabs)
window.addEventListener('storage', function(event) {
console.log('Storage changed:');
console.log('Key:', event.key);
console.log('Old value:', event.oldValue);
console.log('New value:', event.newValue);
console.log('URL:', event.url);
console.log('Storage:', event.storageArea);
});
Storage Limits
  • localStorage: Usually 5-10MB per origin
  • sessionStorage: Same limits but cleared on tab close
  • Data is stored as strings only
  • Synchronous operations (can block UI)

1.5 Storage Wrapper Class

class Storage {
static set(key, value, expiresIn = null) {
const item = {
value,
expires: expiresIn ? Date.now() + expiresIn : null
};
localStorage.setItem(key, JSON.stringify(item));
}

static get(key) {
const item = localStorage.getItem(key);
if (!item) return null;

const parsed = JSON.parse(item);

// Check expiration
if (parsed.expires && Date.now() > parsed.expires) {
this.remove(key);
return null;
}

return parsed.value;
}

static remove(key) {
localStorage.removeItem(key);
}

static clear() {
localStorage.clear();
}
}

// Usage
Storage.set('token', 'abc123', 3600000); // Expires in 1 hour
const token = Storage.get('token');

2. Fetch API

2.1 Basic GET Request

// Simple fetch
fetch('https://api.example.com/users')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));

// With async/await
async function fetchUsers() {
try {
const response = await fetch('https://api.example.com/users');
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Error:', error);
}
}

2.2 Checking Response Status

async function fetchUser(id) {
const response = await fetch(`https://api.example.com/users/${id}`);

if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}

const data = await response.json();
return data;
}

// Handle different status codes
async function fetchWithStatusHandling(url) {
const response = await fetch(url);

if (response.status === 200) {
return await response.json();
} else if (response.status === 404) {
throw new Error('Resource not found');
} else if (response.status === 500) {
throw new Error('Server error');
} else {
throw new Error(`Unexpected status: ${response.status}`);
}
}

2.3 POST Request

async function createUser(userData) {
const response = await fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(userData)
});

if (!response.ok) {
throw new Error('Failed to create user');
}

return await response.json();
}

// Usage
const newUser = {
name: 'John Doe',
email: 'john@example.com'
};

createUser(newUser)
.then(user => console.log('Created:', user))
.catch(error => console.error('Error:', error));

2.4 Other HTTP Methods

// PUT (update)
async function updateUser(id, data) {
const response = await fetch(`https://api.example.com/users/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
return await response.json();
}

// PATCH (partial update)
async function patchUser(id, data) {
const response = await fetch(`https://api.example.com/users/${id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
return await response.json();
}

// DELETE
async function deleteUser(id) {
const response = await fetch(`https://api.example.com/users/${id}`, {
method: 'DELETE'
});
return response.ok;
}

2.5 Request Headers

async function fetchWithAuth(url, token) {
const response = await fetch(url, {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-Custom-Header': 'value'
}
});

return await response.json();
}

2.6 Response Formats

// JSON
const data = await response.json();

// Text
const text = await response.text();

// Blob (for images, files)
const blob = await response.blob();
const imageUrl = URL.createObjectURL(blob);

// ArrayBuffer
const buffer = await response.arrayBuffer();

// FormData
const formData = await response.formData();

2.7 Timeout and AbortController

// Timeout with AbortController
async function fetchWithTimeout(url, timeout = 5000) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);

try {
const response = await fetch(url, {
signal: controller.signal
});
clearTimeout(timeoutId);
return await response.json();
} catch (error) {
if (error.name === 'AbortError') {
throw new Error('Request timeout');
}
throw error;
}
}

// Cancel request manually
const controller = new AbortController();
const signal = controller.signal;

fetch('https://api.example.com/data', { signal })
.then(response => response.json())
.then(data => console.log(data))
.catch(error => {
if (error.name === 'AbortError') {
console.log('Request cancelled');
}
});

// Cancel after 1 second
setTimeout(() => controller.abort(), 1000);

2.8 File Upload

async function uploadFile(file) {
const formData = new FormData();
formData.append('file', file);
formData.append('description', 'Profile picture');

const response = await fetch('https://api.example.com/upload', {
method: 'POST',
body: formData
// Don't set Content-Type header, browser will set it automatically
});

return await response.json();
}

// Usage with file input
fileInput.addEventListener('change', async (event) => {
const file = event.target.files[0];
if (file) {
const result = await uploadFile(file);
console.log('Upload result:', result);
}
});

2.9 API Client Wrapper

class API {
constructor(baseURL, defaultHeaders = {}) {
this.baseURL = baseURL;
this.defaultHeaders = defaultHeaders;
}

async request(endpoint, options = {}) {
const url = `${this.baseURL}${endpoint}`;
const config = {
...options,
headers: {
'Content-Type': 'application/json',
...this.defaultHeaders,
...options.headers
}
};

if (config.body && typeof config.body === 'object') {
config.body = JSON.stringify(config.body);
}

try {
const response = await fetch(url, config);

if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}

return await response.json();
} catch (error) {
console.error('API request failed:', error);
throw error;
}
}

get(endpoint, options = {}) {
return this.request(endpoint, { ...options, method: 'GET' });
}

post(endpoint, body, options = {}) {
return this.request(endpoint, { ...options, method: 'POST', body });
}

put(endpoint, body, options = {}) {
return this.request(endpoint, { ...options, method: 'PUT', body });
}

patch(endpoint, body, options = {}) {
return this.request(endpoint, { ...options, method: 'PATCH', body });
}

delete(endpoint, options = {}) {
return this.request(endpoint, { ...options, method: 'DELETE' });
}
}

// Usage
const api = new API('https://api.example.com', {
'Authorization': 'Bearer token123'
});

const users = await api.get('/users');
const newUser = await api.post('/users', { name: 'John', email: 'john@example.com' });

3. Geolocation API

3.1 Get Current Position

if ('geolocation' in navigator) {
navigator.geolocation.getCurrentPosition(
// Success callback
function(position) {
console.log('Latitude:', position.coords.latitude);
console.log('Longitude:', position.coords.longitude);
console.log('Accuracy:', position.coords.accuracy, 'meters');
console.log('Altitude:', position.coords.altitude);
console.log('Speed:', position.coords.speed);
console.log('Timestamp:', position.timestamp);
},
// Error callback
function(error) {
switch(error.code) {
case error.PERMISSION_DENIED:
console.error('User denied geolocation');
break;
case error.POSITION_UNAVAILABLE:
console.error('Location unavailable');
break;
case error.TIMEOUT:
console.error('Request timeout');
break;
}
},
// Options
{
enableHighAccuracy: true,
timeout: 5000,
maximumAge: 0
}
);
} else {
console.error('Geolocation not supported');
}

3.2 Watch Position

// Continuously track position
const watchId = navigator.geolocation.watchPosition(
function(position) {
console.log('Current position:', position.coords);
updateMap(position.coords.latitude, position.coords.longitude);
},
function(error) {
console.error('Error:', error);
}
);

// Stop watching
navigator.geolocation.clearWatch(watchId);

4. Notifications API

4.1 Request Permission

async function requestNotificationPermission() {
if (!('Notification' in window)) {
console.log('Notifications not supported');
return false;
}

const permission = await Notification.requestPermission();
return permission === 'granted';
}

// Usage
requestNotificationPermission().then(granted => {
if (granted) {
console.log('Notifications enabled');
}
});

4.2 Show Notification

function showNotification(title, options = {}) {
if (Notification.permission === 'granted') {
const notification = new Notification(title, {
body: options.body || '',
icon: options.icon || '/icon.png',
badge: options.badge || '/badge.png',
image: options.image || '',
tag: options.tag || '',
requireInteraction: options.requireInteraction || false,
silent: options.silent || false
});

notification.onclick = function() {
console.log('Notification clicked');
window.focus();
notification.close();
};

// Auto-close after 5 seconds
setTimeout(() => notification.close(), 5000);
}
}

// Usage
showNotification('New Message', {
body: 'You have a new message from John',
icon: '/user-icon.png',
tag: 'message-notification'
});

5. History API

5.1 Navigate History

// Go back
history.back();

// Go forward
history.forward();

// Go to specific position
history.go(-2); // 2 pages back
history.go(1); // 1 page forward

5.2 Push and Replace State

// Add new history entry
history.pushState({ page: 1 }, 'title 1', '/page1');

// Replace current entry
history.replaceState({ page: 2 }, 'title 2', '/page2');

// Listen for popstate
window.addEventListener('popstate', function(event) {
console.log('State:', event.state);
console.log('Location:', location.pathname);

// Update page based on state
if (event.state) {
loadPage(event.state.page);
}
});

5.3 Single Page Application Navigation

function navigate(url, state = {}) {
history.pushState(state, '', url);
loadContent(url);
}

function loadContent(url) {
// Fetch and display content
console.log('Loading:', url);
}

// Handle link clicks
document.addEventListener('click', function(event) {
if (event.target.matches('a[data-spa]')) {
event.preventDefault();
navigate(event.target.href);
}
});

// Handle browser back/forward
window.addEventListener('popstate', function(event) {
loadContent(location.pathname);
});

6. Clipboard API

6.1 Copy to Clipboard

// Modern method
async function copyToClipboard(text) {
try {
await navigator.clipboard.writeText(text);
console.log('Copied to clipboard');
} catch (error) {
console.error('Failed to copy:', error);
}
}

// Usage
copyToClipboard('Hello World');

// Legacy method (fallback)
function copyToClipboardLegacy(text) {
const textarea = document.createElement('textarea');
textarea.value = text;
textarea.style.position = 'fixed';
textarea.style.opacity = '0';
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
}

6.2 Read from Clipboard

async function readFromClipboard() {
try {
const text = await navigator.clipboard.readText();
console.log('Clipboard content:', text);
return text;
} catch (error) {
console.error('Failed to read clipboard:', error);
}
}

7. Intersection Observer API

7.1 Basic Usage

// Create observer
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
console.log('Element is visible:', entry.target);
entry.target.classList.add('visible');
} else {
entry.target.classList.remove('visible');
}
});
}, {
threshold: 0.5, // 50% visible
rootMargin: '0px'
});

// Observe elements
const elements = document.querySelectorAll('.observe-me');
elements.forEach(el => observer.observe(el));

// Stop observing
observer.unobserve(element);
observer.disconnect(); // Stop all observations

7.2 Lazy Loading Images

const imageObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.classList.add('loaded');
imageObserver.unobserve(img);
}
});
});

// HTML: <img data-src="image.jpg" class="lazy-load">
const lazyImages = document.querySelectorAll('img.lazy-load');
lazyImages.forEach(img => imageObserver.observe(img));

7.3 Infinite Scroll

const sentinel = document.querySelector('#sentinel');

const sentinelObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
loadMoreContent();
}
});
});

sentinelObserver.observe(sentinel);

8. Other Useful APIs

8.1 Page Visibility API

document.addEventListener('visibilitychange', function() {
if (document.hidden) {
console.log('Page is hidden');
pauseVideo();
} else {
console.log('Page is visible');
resumeVideo();
}
});

8.2 Online/Offline Detection

window.addEventListener('online', function() {
console.log('Back online');
syncData();
});

window.addEventListener('offline', function() {
console.log('Connection lost');
showOfflineMessage();
});

// Check current status
if (navigator.onLine) {
console.log('Currently online');
} else {
console.log('Currently offline');
}

8.3 Battery API

if ('getBattery' in navigator) {
navigator.getBattery().then(battery => {
console.log('Battery level:', battery.level * 100 + '%');
console.log('Charging:', battery.charging);
console.log('Charging time:', battery.chargingTime);
console.log('Discharging time:', battery.dischargingTime);

battery.addEventListener('levelchange', () => {
console.log('Battery level changed:', battery.level);
});
});
}

8.4 Vibration API

if ('vibrate' in navigator) {
// Vibrate for 200ms
navigator.vibrate(200);

// Pattern: vibrate, pause, vibrate
navigator.vibrate([100, 50, 100]);

// Cancel vibration
navigator.vibrate(0);
}

Summary

In this module, you learned:

  • ✅ Web Storage (localStorage/sessionStorage) for data persistence
  • ✅ Fetch API for HTTP requests and network communication
  • ✅ Geolocation API for location-based features
  • ✅ Notifications API for user notifications
  • ✅ History API for SPA navigation
  • ✅ Clipboard API for copy/paste operations
  • ✅ Intersection Observer for visibility tracking
  • ✅ Other useful browser APIs
Next Steps

In Module 16, you'll learn about Asynchronous JavaScript - Callbacks, the foundation of async programming.


Practice Exercises

  1. Build a notes app using localStorage
  2. Create an API client for a REST API
  3. Implement a location-based weather app
  4. Build a notification system with permission handling
  5. Create a SPA router using History API
  6. Implement lazy loading for images
  7. Build an infinite scroll list
  8. Create an offline-first app with cache

Additional Resources