---
title: "21 — Web APIs — The Browser's Built-in Toolbox"
uid: web-apis
tags: ["web-apis", "browser", "workers", "dom", "fetch", "storage", "roadmap:frontend"]
excerpt: "A Web API isn't a network thing — it's an interface the browser exposes that lets your JavaScript reach beyond the language: the page, the network, the disk, the device."
date: 2026-08-12T18:35:08+0000
source: https://www.aveshina.my.id/en/blog/web-apis
---

"Web API" sounds like a URL you hit and get JSON back, which is why I confused it with REST for so long. In the browser context where the roadmap groups them, it's something else entirely: **an interface the browser exposes that lets my JavaScript reach beyond the language itself — into the page, the network, the disk, the device.** [1] The model that made it click: the browser is a toolbox, and a Web API is each capability inside it, handed to my code as an object.

Here's the whole toolbox laid out, grouped by what each capability lets the code touch:

```figure
<svg viewBox="0 0 740 300" xmlns="http://www.w3.org/2000/svg" class="my-6 w-full max-w-2xl" role="img" aria-label="The browser as a toolbox. A JavaScript box on the left connects through a window frame labelled browser to five drawers on the right. Drawer 1 DOM and page — document. Drawer 2 Network — fetch, XHR. Drawer 3 Storage — localStorage, IndexedDB. Drawer 4 Device — camera, geolocation. Drawer 5 Background — Service Workers, Web Workers.">
  <defs>
    <marker id="warrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto">
      <path d="M0,0 L10,5 L0,10 z" fill="#64748b"/>
    </marker>
  </defs>

  <g font-family="ui-sans-serif, system-ui, sans-serif" text-rendering="geometricPrecision">

    <!-- JavaScript box -->
    <rect x="30" y="120" width="150" height="60" rx="8" fill="#fef9c3" stroke="#ca8a04" stroke-width="1.5"/>
    <text x="105" y="145" font-size="13" font-weight="700" fill="#422006" text-anchor="middle">JavaScript</text>
    <text x="105" y="163" font-size="10" fill="#475569" text-anchor="middle">the language</text>

    <!-- Browser window frame -->
    <rect x="215" y="95" width="110" height="110" rx="8" fill="#e0e7ff" stroke="#6366f1" stroke-width="1.5"/>
    <text x="270" y="142" font-size="13" font-weight="700" fill="#1e1b4b" text-anchor="middle">browser</text>
    <text x="270" y="160" font-size="10" fill="#475569" text-anchor="middle">the host</text>

    <!-- arrow JS -> browser -->
    <path d="M180,150 L213,150" fill="none" stroke="#64748b" stroke-width="1.5" marker-end="url(#warrow)"/>

    <!-- five capability drawers -->
    <g>
      <rect x="370" y="22" width="330" height="44" rx="8" fill="#dcfce7" stroke="#16a34a" stroke-width="1.5"/>
      <text x="535" y="40" font-size="12" font-weight="700" fill="#052e16" text-anchor="middle">DOM &amp; page</text>
      <text x="535" y="57" font-size="10" fill="#475569" text-anchor="middle">document · window · events</text>

      <rect x="370" y="74" width="330" height="44" rx="8" fill="#fce7f3" stroke="#db2777" stroke-width="1.5"/>
      <text x="535" y="92" font-size="12" font-weight="700" fill="#500724" text-anchor="middle">Network</text>
      <text x="535" y="109" font-size="10" fill="#475569" text-anchor="middle">fetch · XMLHttpRequest</text>

      <rect x="370" y="126" width="330" height="44" rx="8" fill="#fef9c3" stroke="#ca8a04" stroke-width="1.5"/>
      <text x="535" y="144" font-size="12" font-weight="700" fill="#422006" text-anchor="middle">Storage</text>
      <text x="535" y="161" font-size="10" fill="#475569" text-anchor="middle">localStorage · sessionStorage · IndexedDB</text>

      <rect x="370" y="178" width="330" height="44" rx="8" fill="#e0e7ff" stroke="#6366f1" stroke-width="1.5"/>
      <text x="535" y="196" font-size="12" font-weight="700" fill="#1e1b4b" text-anchor="middle">Device</text>
      <text x="535" y="213" font-size="10" fill="#475569" text-anchor="middle">camera · microphone · geolocation</text>

      <rect x="370" y="230" width="330" height="44" rx="8" fill="#dcfce7" stroke="#16a34a" stroke-width="1.5"/>
      <text x="535" y="248" font-size="12" font-weight="700" fill="#052e16" text-anchor="middle">Background</text>
      <text x="535" y="265" font-size="10" fill="#475569" text-anchor="middle">Service Workers · Web Workers</text>
    </g>

    <!-- arrows browser -> each drawer -->
    <path d="M325,120 C345,70 350,44 368,44" fill="none" stroke="#64748b" stroke-width="1.2" marker-end="url(#warrow)"/>
    <path d="M325,135 C345,115 350,96 368,96" fill="none" stroke="#64748b" stroke-width="1.2" marker-end="url(#warrow)"/>
    <path d="M325,150 L368,148" fill="none" stroke="#64748b" stroke-width="1.2" marker-end="url(#warrow)"/>
    <path d="M325,165 C345,185 350,200 368,200" fill="none" stroke="#64748b" stroke-width="1.2" marker-end="url(#warrow)"/>
    <path d="M325,180 C345,230 350,252 368,252" fill="none" stroke="#64748b" stroke-width="1.2" marker-end="url(#warrow)"/>
  </g>
</svg>
```

## DOM and the page

The first capability the browser hands over is the page itself. The DOM — the tree of meaning the browser built from the HTML — is exposed as the global document object [1][3]. Every querySelector, every addEventListener, every element.textContent = ... is a call into that object. The wider environment lives one level up as window — the browser tab, its history (window.history), its URL (window.location), the screen size. The mental move I had to make: when I write document.querySelector('.btn'), I'm not doing "JavaScript stuff." I'm calling a method on an object the browser built and gave me. Without the browser host, document doesn't exist — pure JavaScript has no DOM.

## Network

The next drawer is reaching out across the wire from inside the page. fetch() is the modern interface — a promise-based function that takes a URL and returns the response [1][4]. Its older sibling, XMLHttpRequest, still powers a lot of legacy code and libraries; I reach for fetch unless I have a reason not to. This is the corner where the name collision bites hardest: fetch *is* a Web API, and it *does* call network endpoints, so "Web API" in this sense overlaps with "an API on the web." The distinction I keep straight now: the endpoint on the server is *the thing I'm calling*; fetch is *the browser capability that lets me call it.* The first lives on someone's server; the second is built into my tab.

## Storage

Then there's the disk — or rather, a small sandboxed slice of it the browser lets my code write to. Three flavors, picked by how much I need to store and how long it should last [1][4]:

- **localStorage** — small (a few MB), synchronous (it blocks until the write is done), survives forever until cleared. Good for a theme toggle or a dismissed banner.
- **sessionStorage** — same API, wiped when the tab closes.
- **IndexedDB** — a real NoSQL database inside the browser (NoSQL just means it's not the traditional table-and-rows kind), asynchronous (works in the background without blocking), handles large structured data. The one to reach for when "save this for offline" gets serious.

Cookies sit adjacent but aren't really this layer — they're sent with every request, more a network contract than a storage API.

## Device

The drawer I find most surprising is the one that touches hardware. The browser mediates the camera and microphone through navigator.mediaDevices, reads location through navigator.geolocation, senses battery level, vibration, clipboard, fullscreen, notifications [1]. What makes this safe to expose at all is that every one of these is **permission-gated** — the browser prompts the user, the user grants or denies, and my code only ever sees the answer. That mediation is the whole point: my JavaScript never talks to the camera driver directly. It asks the browser, the browser asks the user, the browser reports back.

## Background

The last drawer is the one that breaks the page's default model — that JavaScript runs on a single thread and stops the moment the tab closes. **Web Workers** let me spin up a separate thread for heavy computation, so sorting ten thousand rows doesn't freeze the UI [1]. **Service Workers** are stranger: they run independent of any page, can intercept network requests, and are the engine behind offline-first apps and push notifications [4]. Both are still JavaScript, but they live in a different scope than the page — self instead of window — because there's no DOM in a worker. They're a glimpse of the browser as more than a renderer.

## One thing they share

Every drawer above shares a single trait that took me a while to notice: **these interfaces are not JavaScript.** They're defined by standards bodies (W3C, WHATWG), implemented by each browser, and surfaced to whatever scripting language the browser happens to host [2]. fetch, document, localStorage, geolocation — they exist in the browser whether I write the page in JavaScript, TypeScript, or a language compiled to WASM (WebAssembly — a low-level format browsers can run). My JS calls them; it didn't bring them. That's why their availability is a browser-support question, not a language one.

## How I use this

The payoff of the toolbox model is mostly diagnostic. When a feature "doesn't work," I now ask *which capability is missing*, instead of flailing:

- fetch returns undefined or a CORS error → **Network** drawer, and the issue is usually the server's headers, not my call. (CORS is the browser's rule about which servers a page is allowed to call.)
- Data disappears on refresh → **Storage** drawer, and I picked the wrong one (probably sessionStorage when I meant localStorage).
- Geolocation silently fails → **Device** drawer, and the user denied the prompt (or the page isn't served over HTTPS — most device APIs require it).
- UI janks during a big compute → **Background** drawer, and I should offload to a Worker.

Naming the drawer before reaching for a fix turned "the browser is broken" into a question with a small, finite answer set. That's the whole reason I wrote these notes down.

## References

[1] Mozilla, "Web APIs," MDN Web Docs, 2024. [Online]. Available: [https://developer.mozilla.org/en-US/docs/Web/API](https://developer.mozilla.org/en-US/docs/Web/API)

[2] Refsnes Data, "Web APIs - Introduction," W3Schools, 2024. [Online]. Available: [https://www.w3schools.com/js/js_api_intro.asp](https://www.w3schools.com/js/js_api_intro.asp)

[3] Mozilla, "Document Object Model (DOM)," MDN Web Docs, 2024. [Online]. Available: [https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model)

[4] Google, "Fundamentals of Service Workers," web.dev, 2023. [Online]. Available: [https://web.dev/articles/service-workers](https://web.dev/articles/service-workers)

```quiz
Q: In the browser context, what does "Web API" actually refer to?
- An HTTP endpoint that returns JSON
- An interface the browser exposes, letting JavaScript reach the page, network, storage, and device
correct: 1
explain: In this context a Web API is a browser-provided capability surfaced as an object (document, fetch, localStorage…), not a network endpoint. The name overlap with REST APIs is what makes it confusing.

Q: Why does `document.querySelector` fail in a pure Node.js script with no browser?
- Because Node.js is missing a shim that recreates the browser's `document`
- Because `document` is provided by the browser host, not by the JavaScript language itself
correct: 1
explain: The DOM is a browser capability, not part of JavaScript. Without the browser host, the `document` object simply doesn't exist, no matter what shims you add.

Q: You need to store ~50MB of structured data offline and query it later. Which API fits best?
- localStorage
- IndexedDB
- sessionStorage
correct: 1
explain: IndexedDB is the browser's asynchronous NoSQL store, built for large structured data. localStorage and sessionStorage are tiny, synchronous, and string-only.

Q: A Service Worker differs from the page's main script most fundamentally in that it…
- runs on the main thread alongside the page
- runs independent of any page, can intercept network requests, and has no DOM
correct: 1
explain: Service Workers live outside the page lifecycle and have no access to `window` or `document` — they use `self` instead. That's what lets them cache requests and survive tab navigations.

Q: Device APIs (camera, geolocation) work reliably only over HTTPS primarily because…
- browsers gate sensitive capabilities behind a secure context
- HTTPS makes JavaScript run faster
correct: 0
explain: Permission-gated device APIs require a secure context so the browser can trust the origin before handing the page access to hardware like a camera or microphone.
```
