Web APIs
Fetch API, Web Storage, IndexedDB, Web Workers, and more browser APIs
Essential Web APIs
Browsers provide powerful APIs that enable JavaScript to interact with the network, store data, run background tasks, and access device features. Understanding these APIs is essential for building modern web applications.
APIs Covered
- Fetch API — Modern HTTP requests
- Web Storage — localStorage and sessionStorage
- IndexedDB — Client-side database
- Web Workers — Background threading
- Intersection Observer — Visibility detection
Fetch API
// Basic GET request
const response = await fetch("/api/users");
const users = await response.json();
// POST with JSON body
const newUser = await fetch("/api/users", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ name: "Alice", email: "alice@example.com" }),
});
// Full example with error handling
async function fetchData(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
const contentType = response.headers.get("content-type");
if (contentType?.includes("application/json")) {
return await response.json();
}
return await response.text();
} catch (error) {
if (error.name === "AbortError") {
console.log("Request was cancelled");
} else {
console.error("Fetch failed:", error);
}
throw error;
}
}
// Request with timeout using 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 });
return await response.json();
} finally {
clearTimeout(timeoutId);
}
}
Web Storage (localStorage & sessionStorage)
// localStorage - persists across browser sessions
localStorage.setItem("theme", "dark");
const theme = localStorage.getItem("theme"); // "dark"
localStorage.removeItem("theme");
localStorage.clear(); // Remove all
// Storing objects (must stringify)
const user = { name: "Alice", preferences: { theme: "dark" } };
localStorage.setItem("user", JSON.stringify(user));
const storedUser = JSON.parse(localStorage.getItem("user"));
// sessionStorage - cleared when tab closes
sessionStorage.setItem("tempData", "value");
// Storage wrapper with expiration
const storage = {
set(key, value, ttlMs) {
const item = {
value,
expiry: ttlMs ? Date.now() + ttlMs : null,
};
localStorage.setItem(key, JSON.stringify(item));
},
get(key) {
const itemStr = localStorage.getItem(key);
if (!itemStr) return null;
const item = JSON.parse(itemStr);
if (item.expiry && Date.now() > item.expiry) {
localStorage.removeItem(key);
return null;
}
return item.value;
}
};
storage.set("cache", data, 3600000); // 1 hour TTL
IndexedDB
// Open database
const request = indexedDB.open("MyDatabase", 1);
request.onerror = () => console.error("DB error");
request.onupgradeneeded = (event) => {
const db = event.target.result;
// Create object store (table)
const store = db.createObjectStore("users", { keyPath: "id" });
store.createIndex("email", "email", { unique: true });
};
request.onsuccess = (event) => {
const db = event.target.result;
// Add data
const transaction = db.transaction(["users"], "readwrite");
const store = transaction.objectStore("users");
store.add({ id: 1, name: "Alice", email: "alice@example.com" });
// Read data
const getRequest = store.get(1);
getRequest.onsuccess = () => console.log(getRequest.result);
};
// Promise-based wrapper (simplified)
class DB {
constructor(name, version) {
this.db = null;
this.ready = new Promise((resolve, reject) => {
const request = indexedDB.open(name, version);
request.onsuccess = () => {
this.db = request.result;
resolve(this.db);
};
request.onerror = () => reject(request.error);
});
}
async get(store, key) {
await this.ready;
return new Promise((resolve, reject) => {
const tx = this.db.transaction(store, "readonly");
const request = tx.objectStore(store).get(key);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
}
Web Workers
// main.js - Create and communicate with worker
const worker = new Worker("worker.js");
// Send data to worker
worker.postMessage({ type: "calculate", data: [1, 2, 3, 4, 5] });
// Receive results
worker.onmessage = (event) => {
console.log("Result:", event.data);
};
worker.onerror = (error) => {
console.error("Worker error:", error.message);
};
// Terminate when done
worker.terminate();
// worker.js - Background thread
self.onmessage = (event) => {
const { type, data } = event.data;
if (type === "calculate") {
// Heavy computation without blocking main thread
const result = data.reduce((sum, n) => sum + n, 0);
self.postMessage(result);
}
};
// Inline worker using Blob
const workerCode = `
self.onmessage = (e) => {
const result = e.data * 2;
self.postMessage(result);
};
`;
const blob = new Blob([workerCode], { type: "application/javascript" });
const inlineWorker = new Worker(URL.createObjectURL(blob));
Intersection Observer
// Detect when elements enter viewport
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add("visible");
// Lazy load images
if (entry.target.dataset.src) {
entry.target.src = entry.target.dataset.src;
observer.unobserve(entry.target);
}
}
});
}, {
threshold: 0.1, // Trigger when 10% visible
rootMargin: "50px" // Trigger 50px before entering viewport
});
// Observe elements
document.querySelectorAll(".lazy-image").forEach((img) => {
observer.observe(img);
});
// Infinite scroll
const sentinel = document.getElementById("scroll-sentinel");
const scrollObserver = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) {
loadMoreContent();
}
});
scrollObserver.observe(sentinel);
More Useful APIs
// Clipboard API
await navigator.clipboard.writeText("Copied text");
const text = await navigator.clipboard.readText();
// Geolocation
navigator.geolocation.getCurrentPosition(
(position) => {
console.log(position.coords.latitude, position.coords.longitude);
},
(error) => console.error(error)
);
// Notification API
if (Notification.permission === "granted") {
new Notification("Hello!", { body: "This is a notification" });
} else if (Notification.permission !== "denied") {
const permission = await Notification.requestPermission();
}
// Broadcast Channel (cross-tab communication)
const channel = new BroadcastChannel("my-channel");
channel.postMessage({ type: "update", data: "new data" });
channel.onmessage = (event) => console.log(event.data);
// URL and URLSearchParams
const url = new URL("https://example.com/path?foo=bar");
url.searchParams.set("page", "2");
console.log(url.toString()); // https://example.com/path?foo=bar&page=2
// ResizeObserver
const resizeObserver = new ResizeObserver((entries) => {
for (const entry of entries) {
console.log("Size:", entry.contentRect.width, entry.contentRect.height);
}
});
resizeObserver.observe(element);
💡 Key Takeaways
- • Use Fetch API with async/await for network requests
- • localStorage persists; sessionStorage clears on tab close
- • IndexedDB for large/structured client-side data
- • Web Workers for heavy computation without blocking UI
- • Intersection Observer for lazy loading and scroll effects
- • Always handle errors and check API support