Back to Blog
JavaScriptWeb APIsBrowserDOM

JavaScript Web APIs Guide

Master essential Web APIs in JavaScript. From Storage to Fetch to Clipboard and more.

B
Bootspring Team
Engineering
October 4, 2020
7 min read

Web APIs extend JavaScript with powerful browser capabilities. Here's a guide to the most useful ones.

Fetch API#

1// Basic fetch 2const response = await fetch('/api/users'); 3const data = await response.json(); 4 5// POST request 6const newUser = await fetch('/api/users', { 7 method: 'POST', 8 headers: { 9 'Content-Type': 'application/json', 10 }, 11 body: JSON.stringify({ name: 'Alice', email: 'alice@example.com' }), 12}); 13 14// With error handling 15async function fetchData(url) { 16 const response = await fetch(url); 17 18 if (!response.ok) { 19 throw new Error(`HTTP error: ${response.status}`); 20 } 21 22 return response.json(); 23} 24 25// AbortController for cancellation 26const controller = new AbortController(); 27 28const fetchPromise = fetch('/api/data', { 29 signal: controller.signal, 30}); 31 32// Cancel after 5 seconds 33setTimeout(() => controller.abort(), 5000); 34 35try { 36 const response = await fetchPromise; 37} catch (error) { 38 if (error.name === 'AbortError') { 39 console.log('Fetch aborted'); 40 } 41}

Web Storage API#

1// localStorage - persists across sessions 2localStorage.setItem('user', JSON.stringify({ id: 1, name: 'Alice' })); 3const user = JSON.parse(localStorage.getItem('user')); 4localStorage.removeItem('user'); 5localStorage.clear(); 6 7// sessionStorage - cleared when tab closes 8sessionStorage.setItem('tempData', 'value'); 9sessionStorage.getItem('tempData'); 10 11// Storage event (cross-tab communication) 12window.addEventListener('storage', (event) => { 13 console.log('Storage changed:', { 14 key: event.key, 15 oldValue: event.oldValue, 16 newValue: event.newValue, 17 url: event.url, 18 }); 19}); 20 21// Wrapper with JSON handling 22const storage = { 23 get(key, defaultValue = null) { 24 const item = localStorage.getItem(key); 25 try { 26 return item ? JSON.parse(item) : defaultValue; 27 } catch { 28 return item ?? defaultValue; 29 } 30 }, 31 32 set(key, value) { 33 localStorage.setItem(key, JSON.stringify(value)); 34 }, 35 36 remove(key) { 37 localStorage.removeItem(key); 38 }, 39};

Clipboard API#

1// Write text to clipboard 2async function copyText(text) { 3 try { 4 await navigator.clipboard.writeText(text); 5 console.log('Copied to clipboard'); 6 } catch (error) { 7 console.error('Failed to copy:', error); 8 } 9} 10 11// Read text from clipboard 12async function pasteText() { 13 try { 14 const text = await navigator.clipboard.readText(); 15 console.log('Pasted:', text); 16 return text; 17 } catch (error) { 18 console.error('Failed to paste:', error); 19 } 20} 21 22// Copy image to clipboard 23async function copyImage(blob) { 24 try { 25 await navigator.clipboard.write([ 26 new ClipboardItem({ 27 'image/png': blob, 28 }), 29 ]); 30 } catch (error) { 31 console.error('Failed to copy image:', error); 32 } 33} 34 35// Read images from clipboard 36async function pasteImage() { 37 try { 38 const items = await navigator.clipboard.read(); 39 40 for (const item of items) { 41 if (item.types.includes('image/png')) { 42 const blob = await item.getType('image/png'); 43 return URL.createObjectURL(blob); 44 } 45 } 46 } catch (error) { 47 console.error('Failed to read clipboard:', error); 48 } 49}

Geolocation API#

1// Get current position 2function getCurrentPosition() { 3 return new Promise((resolve, reject) => { 4 if (!navigator.geolocation) { 5 reject(new Error('Geolocation not supported')); 6 return; 7 } 8 9 navigator.geolocation.getCurrentPosition( 10 (position) => { 11 resolve({ 12 latitude: position.coords.latitude, 13 longitude: position.coords.longitude, 14 accuracy: position.coords.accuracy, 15 }); 16 }, 17 (error) => { 18 reject(error); 19 }, 20 { 21 enableHighAccuracy: true, 22 timeout: 5000, 23 maximumAge: 0, 24 } 25 ); 26 }); 27} 28 29// Watch position changes 30function watchPosition(callback) { 31 const watchId = navigator.geolocation.watchPosition( 32 (position) => { 33 callback({ 34 latitude: position.coords.latitude, 35 longitude: position.coords.longitude, 36 }); 37 }, 38 (error) => { 39 console.error('Position error:', error); 40 }, 41 { 42 enableHighAccuracy: true, 43 } 44 ); 45 46 // Return cleanup function 47 return () => navigator.geolocation.clearWatch(watchId); 48}

Notification API#

1// Request permission 2async function requestNotificationPermission() { 3 if (!('Notification' in window)) { 4 console.log('Notifications not supported'); 5 return false; 6 } 7 8 const permission = await Notification.requestPermission(); 9 return permission === 'granted'; 10} 11 12// Show notification 13function showNotification(title, options = {}) { 14 if (Notification.permission !== 'granted') { 15 console.log('Notification permission not granted'); 16 return; 17 } 18 19 const notification = new Notification(title, { 20 body: options.body, 21 icon: options.icon, 22 tag: options.tag, // Replace existing with same tag 23 requireInteraction: options.requireInteraction, 24 data: options.data, 25 }); 26 27 notification.onclick = (event) => { 28 console.log('Notification clicked:', event); 29 window.focus(); 30 notification.close(); 31 }; 32 33 notification.onclose = () => { 34 console.log('Notification closed'); 35 }; 36 37 return notification; 38} 39 40// Usage 41await requestNotificationPermission(); 42showNotification('New Message', { 43 body: 'You have a new message from Alice', 44 icon: '/icon.png', 45});

Intersection Observer API#

1// Lazy loading images 2const imageObserver = new IntersectionObserver( 3 (entries) => { 4 entries.forEach((entry) => { 5 if (entry.isIntersecting) { 6 const img = entry.target; 7 img.src = img.dataset.src; 8 img.classList.remove('lazy'); 9 imageObserver.unobserve(img); 10 } 11 }); 12 }, 13 { 14 rootMargin: '50px 0px', // Start loading 50px before visible 15 threshold: 0.01, 16 } 17); 18 19document.querySelectorAll('img.lazy').forEach((img) => { 20 imageObserver.observe(img); 21}); 22 23// Infinite scroll 24const loadMoreObserver = new IntersectionObserver( 25 (entries) => { 26 if (entries[0].isIntersecting) { 27 loadMoreItems(); 28 } 29 }, 30 { threshold: 1.0 } 31); 32 33loadMoreObserver.observe(document.querySelector('.load-more-trigger')); 34 35// Animation on scroll 36const animateObserver = new IntersectionObserver( 37 (entries) => { 38 entries.forEach((entry) => { 39 if (entry.isIntersecting) { 40 entry.target.classList.add('animate-in'); 41 } 42 }); 43 }, 44 { threshold: 0.2 } 45);

ResizeObserver API#

1// Watch element size changes 2const resizeObserver = new ResizeObserver((entries) => { 3 for (const entry of entries) { 4 const { width, height } = entry.contentRect; 5 console.log(`Element resized: ${width}x${height}`); 6 7 // Responsive logic 8 if (width < 600) { 9 entry.target.classList.add('compact'); 10 } else { 11 entry.target.classList.remove('compact'); 12 } 13 } 14}); 15 16resizeObserver.observe(document.querySelector('.container')); 17 18// Cleanup 19resizeObserver.disconnect(); 20 21// React hook example 22function useElementSize(ref) { 23 const [size, setSize] = useState({ width: 0, height: 0 }); 24 25 useEffect(() => { 26 if (!ref.current) return; 27 28 const observer = new ResizeObserver((entries) => { 29 const { width, height } = entries[0].contentRect; 30 setSize({ width, height }); 31 }); 32 33 observer.observe(ref.current); 34 return () => observer.disconnect(); 35 }, [ref]); 36 37 return size; 38}

MutationObserver API#

1// Watch DOM changes 2const mutationObserver = new MutationObserver((mutations) => { 3 mutations.forEach((mutation) => { 4 if (mutation.type === 'childList') { 5 console.log('Children changed:', mutation.addedNodes, mutation.removedNodes); 6 } 7 if (mutation.type === 'attributes') { 8 console.log('Attribute changed:', mutation.attributeName); 9 } 10 }); 11}); 12 13mutationObserver.observe(document.body, { 14 childList: true, 15 subtree: true, 16 attributes: true, 17 attributeFilter: ['class', 'data-state'], 18}); 19 20// Cleanup 21mutationObserver.disconnect(); 22 23// Wait for element to appear 24function waitForElement(selector, timeout = 5000) { 25 return new Promise((resolve, reject) => { 26 const element = document.querySelector(selector); 27 if (element) { 28 resolve(element); 29 return; 30 } 31 32 const observer = new MutationObserver(() => { 33 const element = document.querySelector(selector); 34 if (element) { 35 observer.disconnect(); 36 resolve(element); 37 } 38 }); 39 40 observer.observe(document.body, { childList: true, subtree: true }); 41 42 setTimeout(() => { 43 observer.disconnect(); 44 reject(new Error(`Element ${selector} not found`)); 45 }, timeout); 46 }); 47}

Broadcast Channel API#

1// Cross-tab communication 2const channel = new BroadcastChannel('app_channel'); 3 4// Send message 5channel.postMessage({ 6 type: 'USER_LOGGED_IN', 7 user: { id: 1, name: 'Alice' }, 8}); 9 10// Receive messages 11channel.onmessage = (event) => { 12 console.log('Received:', event.data); 13 14 if (event.data.type === 'USER_LOGGED_OUT') { 15 // Handle logout in all tabs 16 window.location.href = '/login'; 17 } 18}; 19 20// Close channel 21channel.close();

Web Workers#

1// main.js 2const worker = new Worker('worker.js'); 3 4// Send data to worker 5worker.postMessage({ type: 'PROCESS', data: largeArray }); 6 7// Receive results 8worker.onmessage = (event) => { 9 console.log('Result:', event.data); 10}; 11 12worker.onerror = (error) => { 13 console.error('Worker error:', error); 14}; 15 16// Terminate worker 17worker.terminate(); 18 19// worker.js 20self.onmessage = (event) => { 21 const { type, data } = event.data; 22 23 if (type === 'PROCESS') { 24 const result = heavyComputation(data); 25 self.postMessage(result); 26 } 27}; 28 29function heavyComputation(data) { 30 // CPU-intensive work 31 return data.map(item => item * 2); 32}

Performance API#

1// Measure performance 2performance.mark('start-task'); 3 4// Do something 5await longRunningTask(); 6 7performance.mark('end-task'); 8performance.measure('task-duration', 'start-task', 'end-task'); 9 10// Get measurements 11const measures = performance.getEntriesByName('task-duration'); 12console.log(`Task took ${measures[0].duration}ms`); 13 14// Navigation timing 15const timing = performance.getEntriesByType('navigation')[0]; 16console.log({ 17 dnsLookup: timing.domainLookupEnd - timing.domainLookupStart, 18 tcpConnect: timing.connectEnd - timing.connectStart, 19 ttfb: timing.responseStart - timing.requestStart, 20 domLoad: timing.domContentLoadedEventEnd - timing.responseEnd, 21}); 22 23// Resource timing 24const resources = performance.getEntriesByType('resource'); 25resources.forEach((resource) => { 26 console.log(`${resource.name}: ${resource.duration}ms`); 27});

Best Practices#

Error Handling: ✓ Always check API availability ✓ Handle permission denials ✓ Provide fallbacks ✓ Use try-catch with async APIs Performance: ✓ Use observers over polling ✓ Disconnect observers when done ✓ Debounce frequent callbacks ✓ Use workers for heavy tasks Security: ✓ Validate user input ✓ Handle CORS properly ✓ Be careful with clipboard ✓ Request minimal permissions Compatibility: ✓ Check browser support ✓ Use feature detection ✓ Provide polyfills ✓ Test across browsers

Conclusion#

Web APIs provide powerful browser capabilities. Use Fetch for network requests, Storage for persistence, Observers for efficient DOM watching, and Workers for heavy computation. Always check availability, handle errors gracefully, and clean up resources when done.

Share this article

Help spread the word about Bootspring