# How GeoLibre Builds Proxy Requests to the Python Sidecar at /sidecar

> Learn how GeoLibre builds proxy requests to the Python sidecar. Discover runtime URL resolution for local development or same-origin proxy paths.

- Repository: [Open Geospatial Solutions/GeoLibre](https://github.com/opengeos/GeoLibre)
- Tags: how-to-guide
- Published: 2026-08-05

---

**GeoLibre resolves the sidecar base URL at runtime using `resolveSidecarBaseUrl()` in [`packages/processing/src/sidecar-client.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/processing/src/sidecar-client.ts), choosing between a local development URL (`http://127.0.0.1:8765`) or a same-origin proxy path (`/sidecar`) based on the current protocol, port, and environment.**

GeoLibre's browser-based interface needs to communicate with its Python sidecar for geoprocessing tasks—health checks, Whitebox tools, and vector/raster conversions. Rather than hard-coding host and port combinations, the frontend dynamically constructs request URLs. This approach lets the same codebase run in Vite development, Docker deployments, and the native Tauri desktop app without configuration changes.

## How the Base URL Is Determined

The function `resolveSidecarBaseUrl()` in [`packages/processing/src/sidecar-client.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/processing/src/sidecar-client.ts) implements a cascading decision tree to pick the correct sidecar endpoint.

```ts
// packages/processing/src/sidecar-client.ts
function resolveSidecarBaseUrl(): string {
  const override = explicitSidecarUrl();            // VITE_SIDECAR_URL env var
  if (override) return override;

  // When running in a non‑browser context (e.g. during CI) use the local sidecar.
  if (typeof window === "undefined" || !window.location) {
    return LOCAL_SIDECAR_URL;                      // "http://127.0.0.1:8765"
  }

  const { protocol, hostname, port, origin } = window.location;
  const isTauri = protocol === "tauri:" || hostname === "tauri.localhost";
  const isViteDev = port === "5173";

  // Production or Docker builds are served from an http(s) origin.
  // In those cases the sidecar is reachable via a **same‑origin reverse proxy**
  // that maps "/sidecar" to the real sidecar process, avoiding CORS.
  if (!isTauri && !isViteDev && (protocol === "http:" || protocol === "https:")) {
    return `${origin}/sidecar`;
  }

  // Fallback to the local dev sidecar.
  return LOCAL_SIDECAR_URL;
}

```

The function evaluates three conditions in priority order:

- **Explicit override** — If `VITE_SIDECAR_URL` is set, use that value directly.
- **Tauri desktop or Vite dev server** — When `protocol === "tauri:"` or `port === "5173"`, connect to `LOCAL_SIDECAR_URL` (`http://127.0.0.1:8765`).
- **Docker or production HTTP(S) origin** — Use `${origin}/sidecar` to leverage same-origin reverse proxying, eliminating CORS requirements.

The result is cached in `DEFAULT_SIDECAR_URL` and used for all subsequent sidecar API calls.

## How Requests Are Sent with sidecarFetch

Every sidecar operation flows through a thin wrapper that handles authentication token injection:

```ts
function sidecarFetch(input: string, init?: RequestInit): Promise<Response> {
  if (!sidecarAuthToken) return fetch(input, init); // desktop injects token via nginx
  const headers = new Headers(init?.headers);
  headers.set("X-GeoLibre-Token", sidecarAuthToken);
  return fetch(input, { ...init, headers });
}

```

- **Browser/Docker builds**: `sidecarAuthToken` is `null`, so requests pass through unmodified.
- **Tauri desktop builds**: The Tauri shell generates a token and injects it via nginx; `sidecarFetch` adds the `X-GeoLibre-Token` header.

A typical health check implementation demonstrates the pattern:

```ts
export async function checkSidecarHealth(): Promise<SidecarHealth> {
  const res = await sidecarFetch(`${DEFAULT_SIDECAR_URL}/health`);
  if (!res.ok) throw new Error("Sidecar health check failed");
  return res.json();
}

```

All endpoints—`/algorithms`, `/whitebox/*`, `/vector/*`, `/raster/*`—follow this same construction pattern.

## Server-Side Proxy Configuration

GeoLibre ensures `/sidecar` requests never trigger the SPA router:

```ts
// apps/geolibre-desktop/vite.config.ts (excerpt)
navigateFallbackDenylist: [/^\/sidecar\//, /^\/__geolibre_/, /\/[^/?]+\.[^/]+$/],

```

Two proxy layers handle the actual forwarding:

| Environment | Implementation | Behavior |
|-------------|----------------|----------|
| **Vite dev server** | [`vite-proxy-guard.ts`](https://github.com/opengeos/GeoLibre/blob/main/vite-proxy-guard.ts) middleware | Forwards `/sidecar/*` to the running FastAPI dev server or Docker sidecar container |
| **Production/Docker** | nginx reverse proxy | Maps `/sidecar/*` to `127.0.0.1:8765` with automatic token injection for desktop builds |

This architecture guarantees same-origin requests in production, avoiding CORS preflight overhead and complexity.

## Complete Request Flow

1. UI code imports from `@geolibre/processing` and calls a sidecar function.
2. `resolveSidecarBaseUrl()` evaluates `window.location` to choose between local development URL or `/sidecar` proxy path.
3. The endpoint path (e.g., `/whitebox/tools`) is appended to the base URL.
4. `sidecarFetch` executes `fetch`, adding the auth token only in Tauri desktop builds.
5. Vite middleware or nginx routes the request to the Python FastAPI sidecar.

## Practical Code Examples

### Health Check on Application Startup

```ts
import { checkSidecarHealth } from "@geolibre/processing";

async function ensureSidecar() {
  try {
    const { status } = await checkSidecarHealth();
    console.log("Sidecar status:", status);
  } catch (e) {
    console.error("Sidecar unreachable:", e);
  }
}

```

### Executing a Whitebox Tool

```ts
import { runWhiteboxTool } from "@geolibre/processing";

async function hillshade(input: string, output: string) {
  const jobId = await runWhiteboxTool("hillshade", {
    inputs: [{ name: "dem", path: input }],
    outputs: [{ name: "output", path: output }],
  });
  // Poll job status, retrieve results...
}

```

Both examples automatically use the correct base URL without caller awareness of the deployment context.

## Key Source Files

| File | Purpose |
|------|---------|
| [`packages/processing/src/sidecar-client.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/processing/src/sidecar-client.ts) | URL resolution, auth token handling, and `fetch` wrapper for all sidecar communication |
| [`apps/geolibre-desktop/vite.config.ts`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/vite.config.ts) | Vite configuration with SPA fallback denial for `/sidecar` routes |
| [`apps/geolibre-desktop/vite-proxy-guard.ts`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/vite-proxy-guard.ts) | Development middleware for proxying binary and sidecar requests |
| [`backend/geolibre_server/pyproject.toml`](https://github.com/opengeos/GeoLibre/blob/main/backend/geolibre_server/pyproject.toml) | FastAPI sidecar package definition serving the actual endpoints |

## Summary

- **`resolveSidecarBaseUrl()`** dynamically selects between `http://127.0.0.1:8765` and `${origin}/sidecar` based on environment detection.
- **`sidecarFetch`** provides a unified request interface with optional token authentication for Tauri desktop builds.
- **Same-origin proxying** via Vite middleware or nginx eliminates CORS handling in production deployments.
- The design supports three deployment modes—development, Docker, and Tauri desktop—from a single codebase.

## Frequently Asked Questions

### How does GeoLibre avoid CORS issues between browser and sidecar?

GeoLibre uses same-origin reverse proxying. In production and Docker builds, the browser sends requests to `/sidecar` on its own origin, which nginx forwards to `127.0.0.1:8765`. The browser sees the sidecar as same-origin, so no CORS preflight or headers are required.

### What environment variable overrides the sidecar URL?

Set `VITE_SIDECAR_URL` to force a specific base URL. This bypasses all automatic detection and is useful when running the Vite dev server on non-standard ports or proxying through external infrastructure.

### How does authentication work for the sidecar in desktop builds?

The Tauri shell generates a unique token at startup and configures nginx to inject it as the `X-GeoLibre-Token` header. The `sidecarFetch` function adds this header when `sidecarAuthToken` is populated. Browser builds omit the token entirely.

### Why is `navigateFallbackDenylist` needed in vite.config.ts?

Without this denylist, the Vite PWA plugin's SPA router would intercept `/sidecar/health` and similar paths, returning the single-page application shell instead of proxying to the Python backend. The regex `/^\/sidecar\//` ensures these requests reach the actual sidecar API.