Back to writing

Backend infrastructure / provider normalization / SDKs / 2026

Building PeakHut: provider-normalized mountain infrastructure

PeakHut is the backend I built so my outdoor apps do not have to understand every upstream weather, avalanche, terrain, hut, route, and hazard provider. The apps ask for mountain context. PeakHut owns provider credentials, normalization, routing, freshness, provenance, and SDK contracts.

The interesting engineering problem was not "can I call an API?" It was how to turn many inconsistent provider surfaces into one product-grade platform that native and web apps can trust.

Mountain applications are especially unforgiving here. A user taps a point and expects a coherent answer: what area am I in, what is the latest bulletin, what is the terrain doing, are there alerts, where are the huts, what hazards matter, and what overlays can I put on the map? The source data for those answers is spread across national weather agencies, avalanche services, terrain datasets, public route databases, glacier catalogs, condition reports, and my own generated artifacts.

PeakHut sits between that mess and the client apps. It is a Bun backend with provider adapters, normalized domain models, Postgres persistence, background refresh jobs, protected API routes, raw provider escape hatches, Slab-compatible migration endpoints, and first-party Swift and TypeScript SDKs.

System boundary
Apps get product-shaped mountain data. Providers stay behind the backend.
14 Provider IDs in the platform contract
17 Dataset capability records
2 First-party client SDKs
01 Upstream providers

Meteo-France, SLF, IGN, Copernicus DEM, Camptocamp, GLIMS, Georisques, hut data, condition feeds, and generated hazard artifacts.

02 Adapters

Provider-specific fetching, parsing, attribution, raw passthrough, cache behavior, and failure handling live in the backend.

03 Normalization

Shared records for bulletins, alerts, observations, elevation, routes, hazards, huts, overlays, and provenance.

04 Protected API

Scoped API keys, rate limits, internal cron secrets, OpenAPI output, health state, and background refresh runs.

05 Client SDKs

Swift and TypeScript clients expose app flows without duplicating provider logic or shipping upstream credentials.

The architecture is intentionally backend-heavy. Clients should render maps, routes, and interaction state. PeakHut owns provider logic, freshness, auth, normalization, and cross-app contracts.

The provider problem

Each provider has a different shape, geography, freshness model, auth story, naming system, and failure mode. Some are country-specific. Some are global but lower resolution. Some expose clean JSON. Some are public feeds that need careful parsing. Some are artifacts I generate and publish to object storage.

I did not want every app to re-learn those differences. The backend has a capability registry that describes which provider supports which dataset, where it applies, how fresh it should be, and whether raw passthrough is available. The route resolver can then prefer IGN for French terrain elevation and fall back to Copernicus elsewhere, or choose the right avalanche provider based on geography.

That is a small architecture decision with a large payoff. It makes provider selection explicit, testable, and visible in the response provenance instead of hiding it in app-side conditionals.

Provider routing
The backend treats providers as capabilities, not one-off integrations.
Weather and avalanche Meteo-France + Swiss Avalanche

BRA bulletins, vigilance alerts, nearest observations, Swiss warning regions, and Slab-compatible bulletin migration routes.

avalanche_bulletins alerts observations
Terrain IGN + Copernicus DEM

Provider-aware elevation lookup, LiDAR DEM TileJSON catalogs, and terrain overlay tile generation for existing map clients.

terrain_elevation fallback
Routes and huts Camptocamp + hut records

Route discovery, route detail, waypoints, attribution, searchable enriched hut records, and R2-backed photo URLs.

route_discovery mountain_huts
Hazards GLIMS, Georisques, artifacts

Glacier outlines, velocity samples, snow cover, condition reports, rockfall events, and crevasse susceptibility layers.

route_hazards crevasse_hazards
PeakHut keeps the product API stable even while providers differ by country, dataset, latency, cacheability, and reliability.

The normalized contract

The strongest product decision in PeakHut is that the app does not stitch five endpoints together just to render a map tap. It can call /v1/locations/summary and receive a composed answer: the selected point, elevation, containing areas, nearby areas, primary area, latest bulletin, nearest observation, alerts, and optional GeoJSON overlays.

The route side follows the same pattern. A GPX import can be parsed first, then analyzed, then upgraded into route conditions with map-ready overlays. Specialized hazard endpoints exist when a screen only needs one layer, but the recommended product flows are intentionally bundled.

The responses carry provenance. I want the client to know which provider produced the record, when it was fetched, when it was normalized, and why a resolver picked it. That matters for debugging, user trust, and future provider replacement.

{
  "point": { "latitude": 45.9237, "longitude": 6.8694 },
  "elevation": {
    "provider": "ign",
    "dataset": "terrain_elevation",
    "provenance": {
      "resolver": {
        "strategy": "provider-preferred",
        "rationale": "IGN preferred in France, Copernicus fallback elsewhere"
      }
    }
  },
  "primaryArea": { "id": "mf:mont-blanc", "name": "Mont Blanc" },
  "bulletin": { "overallRisk": 3, "riskLabel": "considerable" },
  "observation": { "stationName": "nearest station" },
  "alerts": [],
  "overlays": {
    "selectedPoint": "GeoJSON FeatureCollection",
    "areas": "GeoJSON FeatureCollection"
  }
}
The exact payload varies by endpoint and available data, but the contract principle stays stable: return product-ready mountain context with explicit provider provenance.

Security and operational boundaries

PeakHut is not just a convenience wrapper. It is where provider credentials and operational controls belong. Public clients should not carry Meteo-France keys, Mapbox-derived tile secrets, cron secrets, or long-lived platform keys when a backend proxy is available.

The service has three auth zones. /health is public. /openapi.json, /ops, /v1/*, and /v2/* are protected with x-api-key and scoped access such as platform.read or platform.route. Internal refresh routes sit behind x-cron-secret. Rate limits separate normal JSON APIs from heavier terrain tile traffic.

That boundary lets me move fast on apps without leaking provider details into them. A map feature can ship against a stable PeakHut method while the backend changes which provider, cache, or artifact source is used underneath.

Backend architecture
PeakHut is a service layer, not a bundle of client helpers.
Runtime Bun HTTP service

Route registration, CORS, validation with Zod, protected path checks, rate limiting, health state, and admin assets.

Bun TypeScript Zod
Persistence Postgres-backed refresh

Provider refresh runs normalize raw records, persist durable state, and expose stale-cache fallback for unreliable sources.

Postgres refresh jobs stale fallback
Operational API Health, OpenAPI, ops UI

Health reports configured app keys and provider capabilities. OpenAPI and the internal ops dashboard make the surface inspectable.

/health /openapi.json /ops
Migration layer Slab-compatible routes

Additive v1 and v2 routes let existing Slab and Traverse consumers move onto PeakHut without changing every app contract at once.

/v1/bulletins /v2/zones terrain tiles
The compatibility layer is deliberate. A platform migration is safer when the new backend can support old contracts while the normalized API grows underneath it.

SDKs as product infrastructure

I built first-party SDKs because the API is only as good as the way apps consume it. The Swift package and TypeScript client are intentionally thin. They preserve the server response shape, expose named product flows, and keep provider logic out of app code.

The Swift SDK exposes async methods like locationSummaryWithOverlays(at:), searchMountainHuts, crevassePoint, parseGPX, routeConditions, and snowCoverTileJSON. The TypeScript SDK mirrors the same flows for web, React Native, SSR, and Node consumers with an injectable fetch for server environments and tests.

I prefer this shape over a heavy abstraction. The SDKs should make the correct endpoint easy to call, not hide the platform. When the API changes, the backend, OpenAPI surface, Swift models, TypeScript models, and API maps move together.

Client stack
The SDKs expose product flows while keeping provider logic centralized.
Native Apple PeakhutSDK Swift package

Codable models and async URLSession calls for iOS and Apple-platform clients. Built for map tap sheets, route analysis, hazard layers, huts, and GPX flows.

Swift Codable async/await
Web and server @peakhut/sdk-web

A fetch-based TypeScript client for browser apps, React Native, SSR, and Node backends. It keeps the JSON contract visible and typed.

TypeScript SSR React Native
Recommended security App backend proxy

Production apps should usually call their own backend, which injects the PeakHut API key server-side. Internal tools can call PeakHut directly.

x-api-key off client scoped keys
Documentation Feature-to-endpoint API maps

The iOS and web maps translate product features into SDK methods and HTTP endpoints so new app work starts from the right contract.

API_MAP.md OpenAPI
The SDKs are not the source of truth. They are typed, ergonomic projections of the backend contract.

The apps this unlocked

PeakHut exists because I kept building outdoor apps that needed the same hard infrastructure. Rather than copy provider clients between apps, I made the backend the source of truth and let each product keep its own interface.

  • Traverse iOS uses PeakHut-style mountain context for map overlays, route planning, avalanche and hazard layers, snow cover, LiDAR terrain, glacier data, huts, and protected terrain-tile access.
  • Traverse Web uses PeakHut through a backend/proxy pattern for protected API access, viewport-scoped glacier outlines, LiDAR region catalogs, snow cover, and map layers without exposing provider credentials in the browser.
  • Slab migration paths are supported with compatibility routes for French bulletins, global avalanche zones, terrain overlays, weather profiles, and alerts so existing consumers can move gradually instead of through a big-bang rewrite.
  • PeakHut SDK clients give future iOS, web, React Native, and server-side projects the same backend contract from day one, with app-specific rendering and caching left to the product.
The portfolio thesis: PeakHut is the kind of infrastructure I like building: a pragmatic platform boundary that removes duplicated provider work from every app, keeps secrets and operational policy server-side, and gives product teams a typed, stable surface to build against.

What I would harden next

The current system already has the important shape: provider adapters, normalized contracts, protected routes, internal jobs, raw diagnostics, compatibility APIs, SDKs, and API maps. The next hardening step is making the OpenAPI schemas fully generation-grade so Swift and TypeScript models can be generated or contract-tested instead of manually mirrored.

I would also keep moving long-running refresh work into a more explicit supervised queue, add stronger provider health scoring, and turn provenance into a richer debugging surface for operators and client developers. The architecture is already set up for that because provider logic is centralized, not scattered across apps.

Built as a backend infrastructure and architecture portfolio case study by Pedro Marques.