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.