GeoLibre API Best Practices: A Complete Guide to Modular, Store‑Driven GIS Development
Use the typed Zustand store as your single source of truth, keep Python sidecar calls optional with graceful fallbacks, and register plugins before UI mount to build robust GeoLibre applications.
The GeoLibre API follows a store‑driven, modular architecture that cleanly separates client‑side JavaScript state management from optional Python backend services. This design enables both lightweight browser‑only deployments and full‑featured desktop applications with heavy‑weight geoprocessing capabilities. Understanding these architectural layers is essential for writing maintainable, performant code according to the opengeos/GeoLibre source.
Core Architecture Overview
GeoLibre organizes functionality into distinct layers, each with clear responsibilities and entry points. Working with—rather than around—these layers ensures state consistency and predictable behavior.
Best Practice 1: Always Work Through the Store
Direct manipulation of MapLibre GL JS instances bypasses GeoLibre's state management and causes UI desynchronization. The useStore Zustand store in [packages/core/src/store.ts](https://github.com/opengeos/GeoLibre/blob/main/packages/core/src/store.ts) serves as the single source of truth.
Store mutations automatically trigger synchronization via MapController.syncLayers. Use the provided helpers rather than raw map operations.
import { useStore } from '@geolibre/core';
import { addGeoJsonLayer } from '@geolibre/map';
async function loadGeoJson(url: string) {
const response = await fetch(url);
const data = await response.json();
const store = useStore.getState();
// Dispatch store action — MapController syncs automatically
addGeoJsonLayer(store, {
id: `geojson-${Date.now()}`,
data,
sourceLayer: '',
paint: { 'fill-color': '#3388ff', 'fill-opacity': 0.5 },
});
}
Best Practice 2: Leverage TypeScript Definitions for API Safety
GeoLibre exports comprehensive types from [packages/core/src/types.ts](https://github.com/opengeos/GeoLibre/blob/main/packages/core/src/types.ts). Import these rather than constructing raw objects to catch errors at compile time.
Key type categories include:
LayerConfig— Complete layer configuration with source, paint, and layout propertiesProjectSchema— Root.geolibre.jsonstructureProcessingTool— Interface for custom processing tools
Best Practice 3: Keep Python Sidecar Usage Optional
The FastAPI sidecar endpoints in [backend/geolibre_server/app/main.py](https://github.com/opengeos/GeoLibre/blob/main/backend/geolibre_server/app/main.py) provide vector (/vector/*), raster (/raster/*), and conversion (/conversion/*) operations. However, not all deployments include this backend.
Always detect sidecar availability before calling /sidecar endpoints. The client falls back to the browser‑based engine (Turf.js, DuckDB‑WASM) automatically. Calling the HTTP API when the sidecar isn't running throws a 502 error.
async function convertRaster(inputPath: string, outputPath: string) {
// Check sidecar health first
const health = await fetch('/sidecar/health').catch(() => null);
if (!health?.ok) {
throw new Error('Python sidecar unavailable; use client-side processing instead');
}
const resp = await fetch('/sidecar/conversion/raster', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ input: inputPath, output: outputPath }),
});
if (!resp.ok) {
const err = await resp.json();
throw new Error(`Conversion failed: ${err.detail}`);
}
return await resp.json(); // { status: 'ok', output: ... }
}
Best Practice 4: Handle Async Errors Uniformly
All GeoLibre API functions return Promises that reject with GeoLibreError. Wrap calls in try...catch blocks and surface the message property to users. Never swallow errors silently.
try {
await process.runTool('buffer', { distance: 1000 });
} catch (error) {
if (error instanceof GeoLibreError) {
showNotification(error.message); // User-facing error
console.error(error.code, error.stack); // Debugging info
}
}
Best Practice 5: Register Plugins Before UI Mount
Plugins load in [apps/geolibre-desktop/src/hooks/usePlugins.ts](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/hooks/usePlugins.ts). Custom plugins must call registerPlugin before React renders the main UI to ensure proper initialization order.
import { registerPlugin } from '@geolibre/plugins';
import MyPlugin from './my-plugin';
// Register before app mount
registerPlugin({
id: 'my-plugin',
name: 'My Awesome Plugin',
component: MyPlugin,
description: 'Provides a custom data source',
});
Plugin registration after mount may cause undefined behavior in layer synchronization.
Best Practice 6: Respect Remote File Size Limits
The remote file formats plugin enforces a 2 GiB limit defined as MAX_REMOTE_FILE_BYTES in [packages/plugins/src/plugins/remote-file-formats.ts](https://github.com/opengeos/GeoLibre/blob/main/packages/plugins/src/plugins/remote-file-formats.ts). Validate file sizes client‑side to avoid opaque failures.
const MAX_REMOTE_FILE_BYTES = 2 * 1024 * 1024 * 1024; // 2 GiB
async function loadRemoteFile(url: string) {
const head = await fetch(url, { method: 'HEAD' });
const size = parseInt(head.headers.get('content-length') || '0');
if (size > MAX_REMOTE_FILE_BYTES) {
throw new Error(`File exceeds 2 GiB limit: ${size} bytes`);
}
// Proceed with loading...
}
Best Practice 7: Use i18n for All User-Facing Strings
GeoLibre uses react-i18next for internationalization. Wrap all UI text with the t() function and add keys to [apps/geolibre-desktop/src/i18n/locales/en.json](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/i18n/locales/en.json) as the source of truth for translations.
import { useTranslation } from 'react-i18next';
function LayerPanel() {
const { t } = useTranslation();
return (
<button onClick={addLayer}>
{t('layers.addButton')} // "Add Layer" or localized equivalent
</button>
);
}
Best Practice 8: Control Embedding via Query Parameters
The embedding API in [packages/embed/src/index.ts](https://github.com/opengeos/GeoLibre/blob/main/packages/embed/src/index.ts) supports reproducible configuration through URL parameters:
| Parameter | Purpose | Example |
|---|---|---|
locale |
Language code | ?locale=de |
theme |
UI theme | ?theme=dark |
center |
Initial map center | ?center=40.7128,-74.0060 |
zoom |
Initial zoom level | ?zoom=12 |
Use these for documentation, reports, and automated testing scenarios.
Summary
- Work through the store — Use
useStoreand helper functions; never manipulate MapLibre directly - Type everything — Import definitions from
@geolibre/corefor compile‑time safety - Make sidecar optional — Detect availability; fall back to client‑side processing
- Handle errors explicitly — Catch
GeoLibreErrorand surface meaningful messages - Register plugins early — Call
registerPluginbefore React mount - Validate file sizes — Respect the 2 GiB
MAX_REMOTE_FILE_BYTESlimit - Internationalize strings — Use
t()with keys from the locales file - Configure embedding — Use query parameters for reproducible deployments
Frequently Asked Questions
How do I check if the Python sidecar is available before making API calls?
Make a lightweight request to /sidecar/health or catch connection errors on your first API call. The desktop app spawns the sidecar on demand, so availability depends on installation status. When unavailable, use the client‑side processing engine in [packages/processing/src/processing.ts](https://github.com/opengeos/GeoLibre/blob/main/packages/processing/src/processing.ts).
Can I use GeoLibre without the Python backend entirely?
Yes. The core JavaScript API provides full functionality for browser‑based GIS workflows. The Python sidecar adds heavy‑weight operations (Whitebox GIS, rasterio, GeoPandas) as an optional enhancement. Design your code to gracefully degrade when the sidecar is absent.
Where should I define custom processing tools?
Extend the processing engine by implementing the ProcessingTool interface and registering via process.registerTool. Place tool definitions in your plugin or in a dedicated package, then import and register them in your application's initialization code before the first tool invocation.
How do I debug layer synchronization issues?
Enable Zustand devtools to inspect store state changes. Verify that your code uses store actions (addGeoJsonLayer, updateLayer, removeLayer) rather than direct MapLibre API calls. Check MapController.syncLayers for the reconciliation logic that applies store changes to the map.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →