Part 8.4 — Client-Driven Data with REST: Avoiding Over-Fetching
Most discussions about over-fetching blame the backend.
That’s only half the story.
Over-fetching happens when clients don’t express intent clearly and servers respond with “everything, just in case.” In this post, you’ll flip the perspective and design React clients that pull only what they need—while keeping your REST APIs explicit, debuggable, and cache-friendly.
1. Client-Driven Does Not Mean Client-Controlled
Important distinction:
Client-driven → the client declares intent
Client-controlled → the client dictates structure
REST APIs should support the former, not the latter.
Your goal is to give the client choices, not power.
2. Start from the UI, Not the Endpoint
Before designing an endpoint, ask:
what does this screen render?
which fields are actually visible?
which interactions require more data?
Example: a users list page.
UI needs:
id
name
avatar
It does not need:
email
preferences
audit history
Design from the screen outward.
3. Use Different Endpoints (or Modes) for Different Screens
Avoid the “one endpoint for everything” mindset.
Good pattern:
GET /users → list (summary)
GET /users/:id → detailEven better with explicit modes:
GET /users?view=summary
GET /users/42?view=detailThis makes intent visible in logs, caches, and metrics.
4. React Fetch Calls Should Be Honest
In React, avoid vague fetches like:
fetch('/api/users');Prefer:
fetch('/api/users?view=summary');This does two things:
documents intent at the call site
prevents accidental over-fetching
Your fetch URL is part of your API contract.
5. Co-locate Data Requirements with Components
Each component should make its data needs obvious.
function UsersList() {
const [users, setUsers] = useState<UserSummary[]>([]);
useEffect(() => {
fetch('/api/users?view=summary')
.then(res => res.json())
.then(setUsers);
}, []);
}This keeps:
data scope local
refactors safe
dependencies explicit
Avoid global “load everything” patterns.
6. Progressive Data Loading Beats Big Payloads
Don’t fetch everything upfront “just in case.”
Instead:
load summaries first
fetch details on navigation or interaction
Example:
list page → summary
detail page → full data
This mirrors how users actually move through the app.
7. Client-Side Composition Is a Feature
GraphQL composes data on the server.
REST encourages composition on the client.
That’s not a flaw—it’s a trade-off.
React is good at:
composing UI
merging responses
handling async flows
Let the client assemble views from:
focused endpoints
predictable shapes
8. Avoid UI-Driven Endpoints
Bad REST design:
GET /dashboardData
GET /homePageStuffThese endpoints:
couple backend to UI
break when UI changes
don’t scale across clients
Design APIs around resources, not screens.
9. Use Query Parameters to Express Variants
Instead of new endpoints:
GET /posts?include=author
GET /posts?include=author,commentsReact components choose what they need.
Server remains:
explicit
bounded
predictable
Validate these parameters strictly—never dynamically expose everything.
10. Don’t Mirror GraphQL on the Client
Avoid building:
dynamic query builders
field selection UIs
ad-hoc include logic
If the client starts “assembling queries,” you’re rebuilding GraphQL poorly.
REST works best with:
known options
limited combinations
strong contracts
11. Error Handling Must Match the Contract
If a client requests:
GET /users/42?view=detailAnd the user doesn’t exist:
return a clear 404
with a predictable error shape
Do not:
return partial data
silently fall back to another view
Consistency beats convenience.
12. Measure Payloads, Not Just Requests
Over-fetching isn’t just about request count.
Watch:
response size
unused fields
parsing cost
Small, focused payloads improve:
performance
battery usage
perceived speed
Especially on mobile.
13. The Client’s Responsibility
A disciplined client:
requests only what it needs
documents intent in URLs
avoids speculative fetching
respects API contracts
When clients behave well, servers can stay simple.
14. Summary
You now know how to:
design React clients that drive data needs
avoid over-fetching without GraphQL
express intent clearly in fetch calls
load data progressively
keep APIs resource-oriented
preserve REST’s strengths
Related
Part 8.3 — Designing REST APIs That Feel Like GraphQL
You don’t need GraphQL to give clients control. This post shows how to design REST APIs that feel flexible, intentional, and predictable—without sacrificing debuggability or caching.
Part 8.2 — GraphQL Mental Model: Schemas, Types & Resolvers (Conceptual)
You don’t need GraphQL to benefit from GraphQL thinking. This post explains schemas, types, and resolvers—and how they map directly to clean REST architecture.
Part 8.1 — Why GraphQL Exists: Problems It Tries to Solve
GraphQL didn’t appear because REST failed. It appeared because many REST APIs were designed without client needs in mind.
Comments