Designing One API for Both a Web Dashboard and a Mobile App
TIL that a REST API originally shaped around the web dashboard's screens needed real redesign, not just new endpoints, once a React Native app started consuming it too.
The Problem #
The API started life serving one dispatch dashboard, so endpoints were shaped around exactly what that dashboard's screens needed — a single /orders endpoint that returned every field the dashboard table displayed, including several the driver app would never use and one field the driver app couldn't get any other way.
Context #
Once a React Native driver app needed the same data, it became clear the API wasn't a neutral data layer — it was an extension of the dashboard's UI, just returned as JSON instead of HTML.
What I Tried #
Adding driver-specific fields onto the existing /orders response and having the mobile app ignore what it didn't need.
What Went Wrong #
The response payload grew for a client (mobile, often on a weaker connection) that needed less data than the client (web, on broadband) it was originally designed for. Any change to serve the dashboard better risked silently breaking a field the driver app depended on, because both were reading the same undocumented, implicit contract.
The Solution #
Split the endpoint by client intent rather than by "resource name": a lean /driver/orders shaped around exactly what the driver app's one core screen needs, and a fuller /dashboard/orders for the dispatcher view — both reading from the same underlying data model, but with distinct, explicit response shapes.
// Same underlying order, two intentionally different views
app.get("/driver/orders", (req, res) => res.json(toDriverView(orders)));
app.get("/dashboard/orders", (req, res) => res.json(toDashboardView(orders)));Why It Works #
A shared backend doesn't require a shared response shape. Treating "the API" as one endpoint per resource conflates the data model (which genuinely should be shared) with the response contract (which shouldn't be, once two clients have meaningfully different needs).
Lessons Learned #
An API design that looks clean for one client can quietly become a liability the moment a second, differently-shaped client depends on it. The fix isn't to make one endpoint serve both — it's to stop assuming "one resource, one endpoint" in the first place.
What I Would Do Differently #
I'd design the response shape per client need from the start, even with only one client, so adding a second client later is a new endpoint, not a renegotiation of an existing one.
Related Concepts #
Backend-for-frontend (BFF) pattern, API versioning, client-specific response shaping.
Related content
Designing a Complete Delivery-Management System as a Solo Developer
TIL that a system spanning a browser extension, a React Native app, an API, and a database only stayed maintainable solo because I treated the API as the one real product and everything else as a thin client of it.
Serving Premium Downloads Without Exposing the File's Real Storage URL
TIL how to replace a guessable direct-download link with a permission-checked, time-limited presigned URL, so the object storage path a purchased asset actually lives at is never sent to the client.
Live Poll Results Without WebSockets: Server-Sent Events in a Node.js API
TIL that once vote writes were race-free, the results screen still felt dead until a manual refresh — switching from client polling to Server-Sent Events made results update instantly without the bidirectional complexity WebSockets would have added for a channel that only ever sends in one direction.