CodingNic

Browser APIs

Geolocation API

Browser APIs 20 min read

Geolocation API

Objectives

By the end of this chapter, you should be able to:

  • Request the user’s location with navigator.geolocation.getCurrentPosition()
  • Read latitude, longitude, and accuracy off the result
  • Handle a user denying the permission request

💡 Why this matters: Anything from “show nearby stores” to “tag a post with a location” needs this. It’s also the first API in this module that requires the user’s explicit permission, a pattern several other browser APIs share.

⚠️ A note on verification: every earlier example in this course was run and its output checked directly. The Geolocation API depends on real device hardware and a user physically clicking “Allow” on a permission prompt, neither of which exists in this sandboxed environment. The code below is accurate and based on the well-documented, standardized Geolocation API, but you’ll want to run it yourself, in an actual browser, to see it firsthand.

What You’re Building

A store locator: one button to find nearby stores, a status area that reports what happened, a map preview image once a location is found, and, by the end, a toggle that tracks the user’s position live instead of just once.

text
store-locator/
├── index.html
└── script.js
html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Store Locator</title>
</head>
<body>
  <h1>Find a Store Near You</h1>
  <button id="findStoresBtn">Find Nearby Stores</button>
  <button id="trackBtn">Start Tracking</button>
  <p id="locationStatus"></p>
  <img id="mapPreview" alt="Map preview of your location" width="300" height="200" style="display: none;">

  <script src="script.js"></script>
</body>
</html>

Everything from here on goes in script.js.

Step 1: Request the Location Once

navigator.geolocation.getCurrentPosition(onSuccess, onError) asks the browser for the user’s current location, triggering a permission prompt the first time a page asks.

javascript
const findStoresBtn = document.getElementById("findStoresBtn");
const locationStatus = document.getElementById("locationStatus");

findStoresBtn.addEventListener("click", () => {
  locationStatus.textContent = "Requesting your location...";

  navigator.geolocation.getCurrentPosition((position) => {
    locationStatus.textContent = `Got it: ${position.coords.latitude}, ${position.coords.longitude}`;
  });
});

Notice getCurrentPosition() isn’t promise-based like fetch(), it’s an older, callback-based API, a success callback as the first argument. This module’s earlier promise-based patterns don’t directly apply here, though you could wrap it in a promise yourself if you wanted await to work with it.

Step 2: Use the Full Position Object

The position passed to the success callback carries a coords object with more than just latitude and longitude.

javascript
navigator.geolocation.getCurrentPosition((position) => {
  const { latitude, longitude, accuracy } = position.coords;
  locationStatus.textContent = `Found you within ${Math.round(accuracy)}m of ${latitude.toFixed(4)}, ${longitude.toFixed(4)}`;
});

accuracy (in meters) matters more than it might seem, a location from Wi-Fi positioning might only be accurate to within a hundred meters, GPS on a phone might be accurate to within a few. Showing it to the user, or at least accounting for it, avoids implying more precision than the reading actually has. A real store locator would send latitude/longitude to a server next (Module 6 covers exactly that), to look up which stores are actually nearby.

Step 3: Show a Map Preview Image

Once you have coordinates, a natural next step is showing them visually instead of just as numbers. Most mapping services offer a “static map” image: a URL that takes a latitude, longitude, and zoom level, and returns an actual map picture, no interactive JavaScript map library required for a simple preview.

javascript
const mapPreview = document.getElementById("mapPreview");

navigator.geolocation.getCurrentPosition((position) => {
  const { latitude, longitude } = position.coords;

  locationStatus.textContent = `Found you: ${latitude.toFixed(4)}, ${longitude.toFixed(4)}`;

  mapPreview.src = `https://your-map-provider.example/static?lat=${latitude}&lon=${longitude}&zoom=14`;
  mapPreview.style.display = "block";
});

The exact URL format depends on whichever map provider a real project uses, this lesson uses a placeholder domain since the specific service and API key are a setup detail outside this course’s scope, not a JavaScript concept. The pattern is what matters: build a URL string from the coordinates you just received, assign it to mapPreview.src, and the browser handles loading and displaying the image the same way it would for any other <img>.

Step 4: Handle Denial and Errors

Users can, and often do, deny a location request, and the request can fail for other reasons too. getCurrentPosition()’s second argument, an error callback, receives an object with a code describing what went wrong.

javascript
findStoresBtn.addEventListener("click", () => {
  locationStatus.textContent = "Requesting your location...";

  navigator.geolocation.getCurrentPosition(
    (position) => {
      const { latitude, longitude } = position.coords;
      locationStatus.textContent = `Found you: ${latitude.toFixed(4)}, ${longitude.toFixed(4)}`;
      mapPreview.src = `https://your-map-provider.example/static?lat=${latitude}&lon=${longitude}&zoom=14`;
      mapPreview.style.display = "block";
    },
    (error) => {
      if (error.code === error.PERMISSION_DENIED) {
        locationStatus.textContent = "Location access denied. Enable it to find nearby stores.";
      } else if (error.code === error.POSITION_UNAVAILABLE) {
        locationStatus.textContent = "Couldn't determine your location right now.";
      } else if (error.code === error.TIMEOUT) {
        locationStatus.textContent = "Location request timed out. Try again.";
      }
    }
  );
});

A store locator that just breaks silently when location access is denied feels broken. Every failure path now leaves #locationStatus with a message explaining what happened, never a blank, confusing screen, and the map image stays hidden rather than showing a broken image icon.

Step 5: Track Location Live

getCurrentPosition() asks once. watchPosition() calls its success callback repeatedly, whenever the device’s position changes, useful for turning this into a live “stores near me as I walk” feature instead of a one-time lookup.

javascript
const trackBtn = document.getElementById("trackBtn");
let watchId = null;

trackBtn.addEventListener("click", () => {
  if (watchId === null) {
    watchId = navigator.geolocation.watchPosition((position) => {
      const { latitude, longitude } = position.coords;
      locationStatus.textContent = `Live position: ${latitude.toFixed(4)}, ${longitude.toFixed(4)}`;
      mapPreview.src = `https://your-map-provider.example/static?lat=${latitude}&lon=${longitude}&zoom=14`;
      mapPreview.style.display = "block";
    });
    trackBtn.textContent = "Stop Tracking";
  } else {
    navigator.geolocation.clearWatch(watchId);
    watchId = null;
    trackBtn.textContent = "Start Tracking";
  }
});

Clicking “Start Tracking” begins watching, locationStatus and mapPreview now update on their own as the device moves, no button click needed for each update. Clicking it again (now reading “Stop Tracking”) calls clearWatch() and resets, the same start/stop shape as setInterval()/clearInterval() (Module 3), just for a stream of location updates instead of a timer.

One More Requirement: HTTPS

Modern browsers only allow geolocation (and most of the other APIs in this module) on pages served over HTTPS, or on localhost during development. A plain http:// page in production simply won’t be allowed to ask, this store locator would need to be served securely to work at all.

The Complete script.js

javascript
const findStoresBtn = document.getElementById("findStoresBtn");
const trackBtn = document.getElementById("trackBtn");
const locationStatus = document.getElementById("locationStatus");
const mapPreview = document.getElementById("mapPreview");
let watchId = null;

function showPosition(position) {
  const { latitude, longitude } = position.coords;
  locationStatus.textContent = `Found you: ${latitude.toFixed(4)}, ${longitude.toFixed(4)}`;
  mapPreview.src = `https://your-map-provider.example/static?lat=${latitude}&lon=${longitude}&zoom=14`;
  mapPreview.style.display = "block";
}

function showError(error) {
  if (error.code === error.PERMISSION_DENIED) {
    locationStatus.textContent = "Location access denied. Enable it to find nearby stores.";
  } else if (error.code === error.POSITION_UNAVAILABLE) {
    locationStatus.textContent = "Couldn't determine your location right now.";
  } else if (error.code === error.TIMEOUT) {
    locationStatus.textContent = "Location request timed out. Try again.";
  }
}

findStoresBtn.addEventListener("click", () => {
  locationStatus.textContent = "Requesting your location...";
  navigator.geolocation.getCurrentPosition(showPosition, showError);
});

trackBtn.addEventListener("click", () => {
  if (watchId === null) {
    watchId = navigator.geolocation.watchPosition(showPosition, showError);
    trackBtn.textContent = "Stop Tracking";
  } else {
    navigator.geolocation.clearWatch(watchId);
    watchId = null;
    trackBtn.textContent = "Start Tracking";
  }
});

Try It

These can’t run in this course’s tooling, write the code and, if you have a moment, try it in an actual browser tab.

  1. Build the findStoresBtn click handler from Step 4, with all three error cases handled.
  2. Add the live tracking toggle from Step 5, and confirm (by reading the code) that clicking it twice in a row starts, then correctly stops, the watch.
  3. Change the success message so it also logs how long the request took, using Date.now() before calling getCurrentPosition() and again inside the success callback.

Recap

  • navigator.geolocation.getCurrentPosition(onSuccess, onError) requests the user’s location once, after a permission prompt, this lesson’s “Find Nearby Stores” button used it directly.
  • The result’s .coords holds latitude, longitude, and accuracy, don’t assume the reading is exact.
  • A returned coordinate pair is real, useful data, showing it as a map image (<img> with its src built from the coordinates) is often more useful to a user than raw numbers.
  • Always handle the error callback, checking error.code against PERMISSION_DENIED, POSITION_UNAVAILABLE, and TIMEOUT, so a denied request never leaves the page looking broken.
  • watchPosition()/clearWatch() track location continuously, the same start/stop pattern as setInterval()/clearInterval(), this lesson’s tracking toggle used exactly that shape.

Next lesson: the Clipboard API, reading from and writing to the system clipboard.