Skip to main content

· 2 min read

Proxy Patterns for Multi-API Dashboards

I worked on a dashboard that needed to consume two distinct APIs with different authentication mechanisms, different error shapes, and different data models. Instead of scattering this complexity across every component, I built a proxy layer that unified the experience.

The Problem

One API handled management concerns: organizations, users, billing. The other handled product features: real-time data, messaging, analytics. The product API was only accessible through the management API’s proxy endpoint, which added another layer of routing logic.

Every component that fetched data needed to know which API to call, how to authenticate, and how to handle errors. This knowledge was duplicated everywhere.

The Solution

I split the data fetching into two hook families, each with its own fetch wrapper, error handling, and type constraints. The management hooks use direct authentication. The product hooks automatically route through the proxy endpoint and inject the required context.

Both families share a common query options builder for cache keys, stale times, and retry logic. But the type signatures are distinct. You can’t accidentally pass a management endpoint to a product hook or vice versa. The compiler catches it.

The key insight was making the product hooks automatically determine whether they should be enabled based on context. If the required context isn’t available yet, the queries disable themselves rather than firing and failing. This eliminated a whole class of race condition bugs during navigation.

Query Options as a Shared Language

I use a builder pattern for query options. Each domain has its own options file that exports functions returning queryOptions objects. These are consumed by both the hooks in components and the route loaders for prefetching.

This means the same configuration drives both the loading state and the prefetch. When you change how a query is cached, the change propagates to both the component fetch and the route loader automatically.

What I Learned

The proxy layer was worth the upfront investment. It moved complexity from “every component” to “one layer,” making the rest of the codebase simpler. New engineers don’t need to understand the routing or authentication differences. They just pick the right hook and the system handles the rest.