Web (Node.js) Examples

Three examples showing how to integrate the Geog API with popular web mapping libraries using a plain Node.js server. Each example renders a vector tile basemap and adds places search with markers.

  • MapLibre GL JS — WebGL-powered vector map with transformRequest for auth
  • Leaflet — Classic Leaflet API with a maplibre-gl-leaflet bridge for vector rendering
  • OpenLayers — Full-featured map framework with a custom tileLoadFunction for auth

Prerequisites

Quick Start

Each web example follows the same steps. Replace maplibre with leaflet or openlayers as needed.

git clone https://github.com/geogdev/examples.git
cd examples/web/maplibre
npm install
cp .env.example .env

Add your API key to .env:

GEOG_API_KEY=your-api-key-here

Start the server:

npm start

Open http://localhost:3000.

Project Structure

All three web examples share the same file layout:

FileDescription
server.jsNode.js HTTP server with token proxy (/api/geog-token) and places proxy (/api/places/search)
lib/geog-client.jsAPI client — handles token exchange and caching at 80% TTL
public/index.htmlMap container and search UI
public/app.jsClient-side map initialization and search logic
public/style.cssMinimal styling
.env.exampleEnvironment variable template

How It Works

Token Proxy

The server keeps the long-lived API key on the backend and exposes two proxy endpoints:

  • GET /api/geog-token — returns a cached short-lived access token (exchanges with the Geog API when the cache is stale)
  • POST /api/places/search — forwards search requests to the Geog API with server-side authentication

The frontend never sees the long-lived API key.

Auth Injection by Library

Each library has a different mechanism for attaching the access token to tile requests:

MapLibre GL JS — uses transformRequest to intercept outgoing requests:

const map = new maplibregl.Map({
  transformRequest: (url) => {
    if (url.startsWith("https://api.geog.dev")) {
      return {
        url,
        headers: { Authorization: `Bearer ${accessToken}` },
      };
    }
  },
  // ...
});

Leaflet — uses the maplibre-gl-leaflet bridge, which wraps MapLibre's WebGL renderer inside a Leaflet layer. Auth is handled through the bridge's transformRequest:

L.maplibreGL({
  style: {
    version: 8,
    sources: {
      geog: {
        type: "vector",
        tiles: [`https://api.geog.dev/v1/tiles/basemap/{z}/{x}/{y}.mvt`],
        maxzoom: 14,
      },
    },
    layers: [/* ... */],
  },
  transformRequest: (url) => {
    if (url.startsWith("https://api.geog.dev")) {
      return { url, headers: { Authorization: `Bearer ${accessToken}` } };
    }
  },
}).addTo(map);

OpenLayers — uses a custom tileLoadFunction since OL doesn't have a global request interceptor:

new VectorTileLayer({
  source: new VectorTileSource({
    tileLoadFunction: function (tile, url) {
      tile.setLoader(function (extent, resolution, projection) {
        fetch(url, {
          headers: { Authorization: "Bearer " + accessToken },
        })
          .then((response) => response.arrayBuffer())
          .then((data) => {
            const features = format.readFeatures(data, { extent, featureProjection: projection });
            tile.setFeatures(features);
          });
      });
    },
    // ...
  }),
});

Full Source

See Also