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.
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.
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.
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.
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.
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.