Browser APIs

50 min read Intermediate

Explore powerful browser APIs for storage, geolocation, notifications, clipboard, history, and more to build feature-rich web applications.

Local Storage & Session Storage

Store data persistently in the browser with Web Storage APIs.

JavaScript
// localStorage - persists across sessions
localStorage.setItem("username", "john_doe");
localStorage.setItem("theme", "dark");

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

localStorage.removeItem("theme");
localStorage.clear(); // Remove all items

// Check if key exists
if (localStorage.getItem("token")) {
    console.log("User is logged in");
}

// Storing objects (must stringify)
const user = { name: "John", age: 30, premium: true };
localStorage.setItem("user", JSON.stringify(user));

const storedUser = JSON.parse(localStorage.getItem("user"));
console.log(storedUser.name); // "John"

// sessionStorage - cleared when tab closes
sessionStorage.setItem("tempData", "value");
const temp = sessionStorage.getItem("tempData");

// Iterate over storage
for (let i = 0; i < localStorage.length; i++) {
    const key = localStorage.key(i);
    console.log(key, localStorage.getItem(key));
}

// Listen for storage changes (from other tabs)
window.addEventListener("storage", (e) => {
    console.log("Key:", e.key);
    console.log("Old value:", e.oldValue);
    console.log("New value:", e.newValue);
    console.log("URL:", e.url);
});

// Wrapper for easier use
const storage = {
    get(key, defaultValue = null) {
        const item = localStorage.getItem(key);
        if (!item) return defaultValue;
        try {
            return JSON.parse(item);
        } catch {
            return item;
        }
    },
    
    set(key, value) {
        localStorage.setItem(key, JSON.stringify(value));
    },
    
    remove(key) {
        localStorage.removeItem(key);
    }
};

storage.set("settings", { theme: "dark", fontSize: 16 });
console.log(storage.get("settings").theme); // "dark"
Storage Limits

LocalStorage typically has a 5-10 MB limit per origin. For larger data, consider IndexedDB. Never store sensitive data (passwords, tokens) in localStorage—it's vulnerable to XSS attacks.

Fetch API

Make HTTP requests to servers and APIs.

JavaScript
// Basic GET request
const response = await fetch("https://api.example.com/users");
const data = await response.json();
console.log(data);

// With error handling
async function fetchData(url) {
    try {
        const response = await fetch(url);
        
        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }
        
        return await response.json();
    } catch (error) {
        console.error("Fetch failed:", error);
        throw error;
    }
}

// POST request
const newUser = await fetch("https://api.example.com/users", {
    method: "POST",
    headers: {
        "Content-Type": "application/json",
        "Authorization": "Bearer token123"
    },
    body: JSON.stringify({
        name: "John",
        email: "john@example.com"
    })
});

// Other HTTP methods
await fetch(url, { method: "PUT", body: JSON.stringify(data) });
await fetch(url, { method: "PATCH", body: JSON.stringify(partial) });
await fetch(url, { method: "DELETE" });

// Response types
const text = await response.text();
const blob = await response.blob();
const buffer = await response.arrayBuffer();
const formData = await response.formData();

// Response properties
console.log(response.ok);         // true if 200-299
console.log(response.status);     // 200
console.log(response.statusText); // "OK"
console.log(response.headers.get("Content-Type"));

// Abort fetch request
const controller = new AbortController();

fetch(url, { signal: controller.signal })
    .then(response => response.json())
    .catch(error => {
        if (error.name === "AbortError") {
            console.log("Request was aborted");
        }
    });

// Cancel after 5 seconds
setTimeout(() => controller.abort(), 5000);

// Upload with progress (using XMLHttpRequest)
function uploadWithProgress(url, file, onProgress) {
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        
        xhr.upload.addEventListener("progress", (e) => {
            if (e.lengthComputable) {
                onProgress(e.loaded / e.total * 100);
            }
        });
        
        xhr.addEventListener("load", () => resolve(xhr.response));
        xhr.addEventListener("error", () => reject(new Error("Upload failed")));
        
        xhr.open("POST", url);
        xhr.send(file);
    });
}

Geolocation API

Get the user's geographic location (with permission).

JavaScript
// Check if supported
if ("geolocation" in navigator) {
    console.log("Geolocation is available");
}

// Get current position
navigator.geolocation.getCurrentPosition(
    (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) => {
        switch (error.code) {
            case error.PERMISSION_DENIED:
                console.log("User denied geolocation");
                break;
            case error.POSITION_UNAVAILABLE:
                console.log("Position unavailable");
                break;
            case error.TIMEOUT:
                console.log("Request timed out");
                break;
        }
    },
    {
        enableHighAccuracy: true, // Use GPS if available
        timeout: 5000,            // Max wait time
        maximumAge: 0             // Don't use cached position
    }
);

// Watch position (continuous updates)
const watchId = navigator.geolocation.watchPosition(
    (position) => {
        updateMap(position.coords.latitude, position.coords.longitude);
    },
    (error) => console.error(error),
    { enableHighAccuracy: true }
);

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

// Promise wrapper
function getLocation() {
    return new Promise((resolve, reject) => {
        navigator.geolocation.getCurrentPosition(resolve, reject);
    });
}

async function showLocation() {
    try {
        const position = await getLocation();
        console.log(position.coords);
    } catch (error) {
        console.error("Location error:", error);
    }
}

Notifications API

Show desktop notifications to users (with permission).

JavaScript
// Check permission status
console.log(Notification.permission);
// "default" | "granted" | "denied"

// Request permission
async function requestNotificationPermission() {
    const permission = await Notification.requestPermission();
    
    if (permission === "granted") {
        console.log("Notifications enabled!");
    } else if (permission === "denied") {
        console.log("Notifications blocked");
    }
}

// Show notification
function showNotification(title, options = {}) {
    if (Notification.permission !== "granted") {
        return;
    }
    
    const notification = new Notification(title, {
        body: "This is the notification body",
        icon: "/icon.png",
        badge: "/badge.png",
        tag: "unique-id",     // Replace notifications with same tag
        requireInteraction: false, // Auto-dismiss
        silent: false,        // Play sound
        data: { url: "/page" } // Custom data
    });
    
    // Event handlers
    notification.onclick = (e) => {
        window.focus();
        window.location.href = e.target.data.url;
        notification.close();
    };
    
    notification.onclose = () => {
        console.log("Notification closed");
    };
    
    notification.onerror = (e) => {
        console.error("Notification error:", e);
    };
    
    // Auto-close after 5 seconds
    setTimeout(() => notification.close(), 5000);
    
    return notification;
}

// Practical example
async function notifyNewMessage(message) {
    if (Notification.permission === "default") {
        await Notification.requestPermission();
    }
    
    if (Notification.permission === "granted") {
        new Notification(`New message from ${message.sender}`, {
            body: message.text,
            icon: message.avatar,
            tag: "message-" + message.id
        });
    }
}

Clipboard API

Read from and write to the system clipboard.

JavaScript
// Write text to clipboard
async function copyToClipboard(text) {
    try {
        await navigator.clipboard.writeText(text);
        console.log("Copied to clipboard!");
    } catch (error) {
        console.error("Failed to copy:", error);
    }
}

// Read text from clipboard
async function pasteFromClipboard() {
    try {
        const text = await navigator.clipboard.readText();
        console.log("Pasted:", text);
        return text;
    } catch (error) {
        console.error("Failed to read clipboard:", error);
    }
}

// Copy button implementation
const copyButton = document.querySelector(".copy-btn");
const codeBlock = document.querySelector("code");

copyButton.addEventListener("click", async () => {
    await copyToClipboard(codeBlock.textContent);
    
    // Visual feedback
    copyButton.textContent = "Copied!";
    setTimeout(() => {
        copyButton.textContent = "Copy";
    }, 2000);
});

// Copy rich content (images, HTML)
async function copyImage(imageBlob) {
    try {
        const item = new ClipboardItem({
            "image/png": imageBlob
        });
        await navigator.clipboard.write([item]);
    } catch (error) {
        console.error("Failed to copy image:", error);
    }
}

// Read clipboard content types
async function readClipboard() {
    try {
        const items = await navigator.clipboard.read();
        
        for (const item of items) {
            console.log("Types:", item.types);
            
            if (item.types.includes("text/plain")) {
                const blob = await item.getType("text/plain");
                const text = await blob.text();
                console.log("Text:", text);
            }
            
            if (item.types.includes("image/png")) {
                const blob = await item.getType("image/png");
                const img = document.createElement("img");
                img.src = URL.createObjectURL(blob);
                document.body.appendChild(img);
            }
        }
    } catch (error) {
        console.error("Clipboard read failed:", error);
    }
}

// Fallback for older browsers
function copyTextFallback(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);
}

History API

Manipulate browser history for single-page applications.

JavaScript
// Navigate through history
history.back();     // Go back one page
history.forward();  // Go forward one page
history.go(-2);     // Go back 2 pages
history.go(1);      // Go forward 1 page

// Number of entries in history
console.log(history.length);

// Push new state (adds to history)
history.pushState(
    { page: "about" },    // State object
    "",                    // Title (ignored by most browsers)
    "/about"               // New URL
);

// Replace current state (doesn't add to history)
history.replaceState(
    { page: "home" },
    "",
    "/home"
);

// Listen for navigation (back/forward buttons)
window.addEventListener("popstate", (event) => {
    console.log("State:", event.state);
    
    if (event.state) {
        loadPage(event.state.page);
    }
});

// Simple SPA router
class Router {
    constructor() {
        this.routes = {};
        
        window.addEventListener("popstate", (e) => {
            this.navigate(location.pathname, false);
        });
    }
    
    register(path, handler) {
        this.routes[path] = handler;
    }
    
    navigate(path, addToHistory = true) {
        const handler = this.routes[path];
        
        if (handler) {
            if (addToHistory) {
                history.pushState({ path }, "", path);
            }
            handler();
        }
    }
}

const router = new Router();

router.register("/", () => {
    document.getElementById("app").innerHTML = "

Home

"; }); router.register("/about", () => { document.getElementById("app").innerHTML = "

About

"; }); // Navigate programmatically router.navigate("/about"); // Handle link clicks document.addEventListener("click", (e) => { if (e.target.matches("[data-link]")) { e.preventDefault(); router.navigate(e.target.getAttribute("href")); } });

Other Useful APIs

JavaScript
// ===== Intersection Observer (visibility detection) =====
const observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
        if (entry.isIntersecting) {
            entry.target.classList.add("visible");
            observer.unobserve(entry.target); // Stop observing
        }
    });
}, {
    threshold: 0.5 // Trigger when 50% visible
});

document.querySelectorAll(".animate-on-scroll").forEach(el => {
    observer.observe(el);
});

// ===== Performance API =====
// Measure execution time
performance.mark("start");
// ... some code ...
performance.mark("end");
performance.measure("myOperation", "start", "end");

const measures = performance.getEntriesByName("myOperation");
console.log(`Took ${measures[0].duration}ms`);

// Navigation timing
const timing = performance.getEntriesByType("navigation")[0];
console.log(`Page load: ${timing.loadEventEnd - timing.startTime}ms`);

// ===== Screen API =====
console.log(`Screen: ${screen.width}x${screen.height}`);
console.log(`Available: ${screen.availWidth}x${screen.availHeight}`);
console.log(`Color depth: ${screen.colorDepth}`);

// Fullscreen
async function toggleFullscreen(element) {
    if (!document.fullscreenElement) {
        await element.requestFullscreen();
    } else {
        await document.exitFullscreen();
    }
}

// ===== Vibration API (mobile) =====
navigator.vibrate(200);          // Vibrate 200ms
navigator.vibrate([100, 50, 100]); // Pattern: vibrate-pause-vibrate
navigator.vibrate(0);            // Stop vibration

// ===== Share API (mobile) =====
async function shareContent() {
    if (navigator.share) {
        try {
            await navigator.share({
                title: "Check this out!",
                text: "An interesting article",
                url: "https://example.com"
            });
        } catch (error) {
            console.log("Share cancelled");
        }
    }
}

// ===== MediaDevices API =====
async function getCamera() {
    try {
        const stream = await navigator.mediaDevices.getUserMedia({
            video: true,
            audio: true
        });
        
        const video = document.querySelector("video");
        video.srcObject = stream;
        video.play();
    } catch (error) {
        console.error("Camera access denied:", error);
    }
}

// ===== Web Speech API =====
// Text to speech
const utterance = new SpeechSynthesisUtterance("Hello, world!");
speechSynthesis.speak(utterance);

// Speech recognition
const recognition = new webkitSpeechRecognition();
recognition.onresult = (event) => {
    console.log("You said:", event.results[0][0].transcript);
};
recognition.start();

Web Workers

Web Workers allow running JavaScript in background threads, keeping the main thread responsive for UI interactions. They're ideal for heavy computations, data processing, or any CPU-intensive tasks.

Basic Worker Setup

JavaScript - Main Thread (app.js)
// Create a new worker
const worker = new Worker('worker.js');

// Send data to worker
worker.postMessage({
    type: 'PROCESS_DATA',
    data: largeArray
});

// Receive results from worker
worker.onmessage = (event) => {
    console.log('Processed result:', event.data);
    updateUI(event.data);
};

// Handle errors
worker.onerror = (error) => {
    console.error('Worker error:', error.message);
    console.error('At line:', error.lineno);
};

// Terminate worker when done
function cleanup() {
    worker.terminate();
}
JavaScript - Worker Thread (worker.js)
// worker.js - runs in separate thread
self.onmessage = (event) => {
    const { type, data } = event.data;
    
    switch (type) {
        case 'PROCESS_DATA':
            const result = processLargeData(data);
            self.postMessage(result);
            break;
            
        case 'STOP':
            self.close(); // Worker terminates itself
            break;
    }
};

function processLargeData(array) {
    // Heavy computation here won't block the UI
    return array
        .map(item => complexCalculation(item))
        .filter(item => item.valid)
        .reduce((acc, item) => acc + item.value, 0);
}

// Workers can import scripts
importScripts('utils.js', 'lodash.min.js');

Inline Workers with Blob

JavaScript - Inline Worker
// Create worker without separate file
function createInlineWorker(fn) {
    const blob = new Blob(
        [`self.onmessage = ${fn.toString()}`],
        { type: 'application/javascript' }
    );
    return new Worker(URL.createObjectURL(blob));
}

// Usage
const worker = createInlineWorker((e) => {
    const result = e.data.numbers.reduce((a, b) => a + b, 0);
    self.postMessage({ sum: result });
});

worker.postMessage({ numbers: [1, 2, 3, 4, 5] });
worker.onmessage = (e) => console.log('Sum:', e.sum);

// Transferable objects (zero-copy transfer)
const buffer = new ArrayBuffer(1024 * 1024); // 1MB
worker.postMessage(buffer, [buffer]); // Transfer, not copy
console.log(buffer.byteLength); // 0 - ownership transferred

Service Workers

Service Workers act as proxy servers between your app and the network. They enable offline functionality, push notifications, and background sync.

Registration and Lifecycle

JavaScript - Service Worker Registration
// Register service worker (in main app)
if ('serviceWorker' in navigator) {
    window.addEventListener('load', async () => {
        try {
            const registration = await navigator.serviceWorker.register('/sw.js', {
                scope: '/' // Control pages at this path
            });
            
            console.log('SW registered:', registration.scope);
            
            // Check for updates
            registration.addEventListener('updatefound', () => {
                const newWorker = registration.installing;
                newWorker.addEventListener('statechange', () => {
                    if (newWorker.state === 'installed') {
                        if (navigator.serviceWorker.controller) {
                            // New version available
                            showUpdateNotification();
                        }
                    }
                });
            });
        } catch (error) {
            console.error('SW registration failed:', error);
        }
    });
}

// Communicate with service worker
navigator.serviceWorker.controller?.postMessage({
    type: 'SKIP_WAITING'
});

Caching Strategies

JavaScript - Service Worker (sw.js)
// sw.js - Service Worker file
const CACHE_NAME = 'app-cache-v1';
const ASSETS = [
    '/',
    '/index.html',
    '/css/style.css',
    '/js/app.js',
    '/offline.html'
];

// Install: cache static assets
self.addEventListener('install', (event) => {
    event.waitUntil(
        caches.open(CACHE_NAME)
            .then(cache => cache.addAll(ASSETS))
            .then(() => self.skipWaiting())
    );
});

// Activate: clean up old caches
self.addEventListener('activate', (event) => {
    event.waitUntil(
        caches.keys().then(keys => {
            return Promise.all(
                keys.filter(key => key !== CACHE_NAME)
                    .map(key => caches.delete(key))
            );
        }).then(() => self.clients.claim())
    );
});

// Fetch: network-first with cache fallback
self.addEventListener('fetch', (event) => {
    event.respondWith(
        fetch(event.request)
            .then(response => {
                // Clone and cache successful responses
                const clone = response.clone();
                caches.open(CACHE_NAME)
                    .then(cache => cache.put(event.request, clone));
                return response;
            })
            .catch(() => {
                // Network failed, try cache
                return caches.match(event.request)
                    .then(cached => cached || caches.match('/offline.html'));
            })
    );
});

IndexedDB

IndexedDB is a low-level API for storing large amounts of structured data. Unlike localStorage, it can store files, blobs, and handles complex queries with indexes.

Database Setup

JavaScript - IndexedDB Wrapper
// Promise-based IndexedDB wrapper
class Database {
    constructor(name, version) {
        this.name = name;
        this.version = version;
        this.db = null;
    }
    
    async open(upgradeCallback) {
        return new Promise((resolve, reject) => {
            const request = indexedDB.open(this.name, this.version);
            
            request.onerror = () => reject(request.error);
            request.onsuccess = () => {
                this.db = request.result;
                resolve(this);
            };
            
            request.onupgradeneeded = (event) => {
                this.db = event.target.result;
                upgradeCallback(this.db, event.oldVersion);
            };
        });
    }
    
    async add(storeName, data) {
        return this.transaction(storeName, 'readwrite', store => {
            return store.add(data);
        });
    }
    
    async put(storeName, data) {
        return this.transaction(storeName, 'readwrite', store => {
            return store.put(data);
        });
    }
    
    async get(storeName, key) {
        return this.transaction(storeName, 'readonly', store => {
            return store.get(key);
        });
    }
    
    async getAll(storeName, query = null) {
        return this.transaction(storeName, 'readonly', store => {
            return store.getAll(query);
        });
    }
    
    async delete(storeName, key) {
        return this.transaction(storeName, 'readwrite', store => {
            return store.delete(key);
        });
    }
    
    transaction(storeName, mode, callback) {
        return new Promise((resolve, reject) => {
            const tx = this.db.transaction(storeName, mode);
            const store = tx.objectStore(storeName);
            const request = callback(store);
            
            request.onsuccess = () => resolve(request.result);
            request.onerror = () => reject(request.error);
        });
    }
}

// Usage
const db = new Database('MyApp', 1);
await db.open((database, oldVersion) => {
    if (oldVersion < 1) {
        const store = database.createObjectStore('users', { keyPath: 'id', autoIncrement: true });
        store.createIndex('email', 'email', { unique: true });
        store.createIndex('createdAt', 'createdAt');
    }
});

await db.add('users', { name: 'Alice', email: 'alice@example.com', createdAt: Date.now() });
const users = await db.getAll('users');

Intersection Observer

Intersection Observer efficiently detects when elements enter or leave the viewport. It's perfect for lazy loading, infinite scroll, and scroll-triggered animations.

Lazy Loading Images

JavaScript - Lazy Load Implementation
// Create observer for lazy loading
const imageObserver = new IntersectionObserver((entries, observer) => {
    entries.forEach(entry => {
        if (entry.isIntersecting) {
            const img = entry.target;
            
            // Load the actual image
            img.src = img.dataset.src;
            if (img.dataset.srcset) {
                img.srcset = img.dataset.srcset;
            }
            
            img.classList.add('loaded');
            observer.unobserve(img); // Stop observing once loaded
        }
    });
}, {
    root: null,           // Viewport
    rootMargin: '50px',   // Load slightly before visible
    threshold: 0          // Trigger as soon as 1px is visible
});

// Observe all lazy images
document.querySelectorAll('img[data-src]').forEach(img => {
    imageObserver.observe(img);
});

// HTML:
// <img data-src="large-image.jpg" data-srcset="small.jpg 400w, large.jpg 800w" alt="...">

Infinite Scroll

JavaScript - Infinite Scroll
// Infinite scroll implementation
class InfiniteScroll {
    constructor(container, loadMore) {
        this.container = container;
        this.loadMore = loadMore;
        this.loading = false;
        this.page = 1;
        
        // Create sentinel element
        this.sentinel = document.createElement('div');
        this.sentinel.className = 'scroll-sentinel';
        container.appendChild(this.sentinel);
        
        // Observer for the sentinel
        this.observer = new IntersectionObserver(
            this.handleIntersect.bind(this),
            { rootMargin: '100px' }
        );
        
        this.observer.observe(this.sentinel);
    }
    
    async handleIntersect(entries) {
        const entry = entries[0];
        
        if (entry.isIntersecting && !this.loading) {
            this.loading = true;
            this.showLoader();
            
            try {
                const items = await this.loadMore(this.page);
                
                if (items.length > 0) {
                    this.appendItems(items);
                    this.page++;
                } else {
                    this.observer.disconnect();
                    this.showEndMessage();
                }
            } catch (error) {
                console.error('Load failed:', error);
            } finally {
                this.loading = false;
                this.hideLoader();
            }
        }
    }
    
    appendItems(items) {
        const fragment = document.createDocumentFragment();
        items.forEach(item => {
            fragment.appendChild(this.createItemElement(item));
        });
        this.container.insertBefore(fragment, this.sentinel);
    }
    
    createItemElement(item) {
        const div = document.createElement('div');
        div.className = 'item';
        div.textContent = item.title;
        return div;
    }
    
    showLoader() { /* ... */ }
    hideLoader() { /* ... */ }
    showEndMessage() { /* ... */ }
}

// Usage
new InfiniteScroll(
    document.getElementById('feed'),
    async (page) => {
        const res = await fetch(`/api/items?page=${page}`);
        return res.json();
    }
);

Scroll Animations

JavaScript - Scroll-Triggered Animations
// Animate elements when they enter viewport
const animationObserver = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
        if (entry.isIntersecting) {
            const el = entry.target;
            const animation = el.dataset.animation || 'fade-in';
            const delay = el.dataset.delay || '0';
            
            el.style.animationDelay = `${delay}ms`;
            el.classList.add('animate', animation);
            
            // Optional: unobserve after animation
            animationObserver.unobserve(el);
        }
    });
}, {
    threshold: 0.2 // 20% visible before triggering
});

// Observe animated elements
document.querySelectorAll('[data-animation]').forEach(el => {
    animationObserver.observe(el);
});

// Multiple thresholds for progress tracking
const progressObserver = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
        // intersectionRatio tells you how much is visible
        const visibility = Math.round(entry.intersectionRatio * 100);
        entry.target.style.setProperty('--visibility', visibility);
    });
}, {
    threshold: Array.from({ length: 101 }, (_, i) => i / 100) // 0, 0.01, 0.02, ... 1
});
Browser API Best Practices
  • Feature detection: Always check if an API exists before using it
  • Permissions: Request permissions at the right time with context
  • Error handling: APIs can fail for many reasons - always handle errors
  • Fallbacks: Provide graceful degradation for unsupported browsers

Summary

Web Storage

localStorage, sessionStorage for data persistence

Fetch API

HTTP requests, async/await, AbortController

Geolocation

Get user location with permission

Notifications

Desktop notifications with permission

Clipboard

Read/write system clipboard

History

SPA routing, pushState, popstate