Data Fetching Differences — REST vs GraphQL
In this tutorial, you'll learn about Data Fetching Differences. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
REST and GraphQL differ fundamentally in how clients fetch data: REST uses multiple fixed endpoints returning predefined structures, while GraphQL uses a single endpoint where clients declare exactly what fields they need.
What You'll Learn
By the end of this lesson, you will compare REST and GraphQL data fetching approaches, analyze payload efficiency, and understand when each approach is better.
Why It Matters
Data fetching strategy directly impacts application performance, bandwidth usage, and developer experience. Choosing the wrong approach leads to slow apps and frustrated developers.
Real-World Use
A mobile app using REST for a user profile screen may fetch /user/1, /user/1/posts, /user/1/followers in 3 separate calls. The same app using GraphQL does it in one query.
Data Fetching Flow Comparison
sequenceDiagram
participant Mobile as Mobile App
participant REST as REST API
participant GQL as GraphQL API
Mobile->>REST: GET /user/1
REST->>Mobile: {"id":1,"name":"Alice","email":"..."}
Mobile->>REST: GET /user/1/posts
REST->>Mobile: [{"id":10,"title":"..."}]
Mobile->>REST: GET /user/1/followers
REST->>Mobile: [{"id":2,"name":"Bob"}]
Mobile->>GQL: POST /graphql {user(id:1){name posts{title} followers{name}}}
GQL->>Mobile: {"data":{"user":{"name":"Alice","posts":[...],"followers":[...]}}}
Payload Size Comparison
# payload_comparison.py
from typing import Any, Dict, List
class PayloadComparator:
def __init__(self):
self.rest_calls: List[Dict] = []
self.gql_result: Dict = {}
def rest_get(self, endpoint: str) -> Dict:
data = {
"/user/1": {"id": 1, "name": "Alice", "email": "a@x.com",
"phone": "555-0100", "address": "123 St",
"created_at": "2024-01-01"},
"/user/1/posts": [{"id": 10, "title": "Post 1",
"body": "content...", "user_id": 1}],
"/user/1/followers": [{"id": 2, "name": "Bob", "email": "b@x.com"}],
}
result = data.get(endpoint, {})
self.rest_calls.append({"endpoint": endpoint, "size": len(str(result))})
return result
def graphql_query(self, fields: List[str], user_id: int = 1) -> Dict:
user_data = {
"id": 1, "name": "Alice", "email": "a@x.com",
"phone": "555-0100", "address": "123 St",
}
result = {}
for f in fields:
if f in user_data:
result[f] = user_data[f]
self.gql_result = {"data": result}
return self.gql_result
def compare(self) -> Dict:
rest_total = sum(c["size"] for c in self.rest_calls)
gql_size = len(str(self.gql_result))
rest_calls = len(self.rest_calls)
return {
"rest_calls": rest_calls,
"rest_total_bytes": rest_total,
"graphql_calls": 1,
"graphql_bytes": gql_size,
"ratio": f"{rest_total / gql_size:.1f}x",
}
comp = PayloadComparator()
comp.rest_get("/user/1")
comp.rest_get("/user/1/posts")
comp.rest_get("/user/1/followers")
comp.graphql_query(["name", "email"])
stats = comp.compare()
print(f"REST: {stats['rest_calls']} calls, {stats['rest_total_bytes']} bytes")
print(f"GraphQL: {stats['graphql_calls']} call, {stats['graphql_bytes']} bytes")
print(f"Ratio: {stats['ratio']} (REST sends more data)")
Expected output:
REST: 3 calls, <n> bytes
GraphQL: 1 call, <n> bytes
Ratio: <n>.x (REST sends more data)
Fixed vs Declarative Fetching
# fixed_vs_declarative.py
from typing import Any, Dict, List
class UserAPI:
@staticmethod
def rest(user_id: int) -> Dict:
return {
"id": user_id,
"name": "Alice",
"email": "a@x.com",
"phone": "555-0100",
"address": "123 Main St",
"age": 30,
"role": "admin",
"created_at": "2024-01-01",
"updated_at": "2024-06-01",
}
@staticmethod
def graphql(user_id: int, fields: List[str]) -> Dict:
full = {
"id": user_id,
"name": "Alice",
"email": "a@x.com",
"phone": "555-0100",
"address": "123 Main St",
"age": 30,
}
return {f: full[f] for f in fields if f in full}
api = UserAPI()
user_id = 1
# REST returns everything
rest_resp = api.rest(user_id)
print(f"REST payload fields: {len(rest_resp)}")
print(f"REST needed only name+email but got: {list(rest_resp.keys())}")
# GraphQL returns only requested fields
gql_resp = api.graphql(user_id, ["name", "email"])
print(f"GraphQL payload fields: {len(gql_resp)}")
print(f"GraphQL returns: {list(gql_resp.keys())}")
Expected output:
REST payload fields: 9
REST needed only name+email but got: ['id', 'name', 'email', 'phone', 'address', 'age', 'role', 'created_at', 'updated_at']
GraphQL payload fields: 2
GraphQL returns: ['name', 'email']
Common Mistakes
1. Ignoring Mobile Constraints
Mobile apps with REST may over-fetch data, wasting bandwidth and battery. GraphQL's declarative fetching is better for mobile.
2. Over-Engineering REST for Data Selection
Adding ?fields=name,email to REST endpoints mimics GraphQL poorly. Stick to standard REST or adopt GraphQL.
3. Not Batching REST Calls
Without batching, multiple REST calls create Waterfall latency. Use parallel requests where possible.
4. Fetching Too Much GraphQL Data
Clients can request deeply nested GraphQL queries that fetch excessive data. Set complexity limits.
5. Not Using Persisted Queries
Large GraphQL query strings can be bigger than REST payloads. Use persisted queries to reduce overhead.
Practice Questions
1. How many HTTP calls does a REST client need for user + posts + followers?
Three separate GET requests to different endpoints.
2. How many calls does GraphQL need for the same data?
One single POST request to /graphql.
3. What is over-fetching in REST?
Receiving more fields than needed because the server controls the response structure.
4. What is the trade-off of GraphQL's single endpoint?
No HTTP Caching per endpoint. Caching must happen at the resolver or CDN level.
Challenge
Build a data fetching cost calculator that compares REST vs GraphQL for a given set of resources and fields, showing total bytes and round trips.
FAQ
Mini Project: Data Fetching Simulator
# fetch_simulator.py
from typing import Dict, List
class FetchSimulator:
def rest_fetch(self, endpoints: List[str], data_store: Dict) -> Dict:
results = {}
total_size = 0
for ep in endpoints:
data = data_store.get(ep, {})
results[ep] = data
total_size += len(str(data))
return {"calls": len(endpoints), "total_bytes": total_size, "data": results}
def graphql_fetch(self, fields: List[str], data_store: Dict) -> Dict:
full = data_store.get("full", {})
result = {f: full[f] for f in fields if f in full}
return {"calls": 1, "total_bytes": len(str(result)), "data": result}
sim = FetchSimulator()
store = {
"/user": {"id": 1, "name": "Alice", "email": "a@x.com", "age": 30},
"/user/posts": [{"id": 1, "title": "Post"}],
"full": {"id": 1, "name": "Alice", "email": "a@x.com", "age": 30},
}
rest = sim.rest_fetch(["/user", "/user/posts"], store)
gql = sim.graphql_fetch(["name", "email"], store)
print(f"REST: {rest['calls']} calls, {rest['total_bytes']} bytes")
print(f"GQL: {gql['calls']} call, {gql['total_bytes']} bytes")
Expected output:
REST: 2 calls, <n> bytes
GQL: 1 call, <n> bytes
What's Next
You understand data fetching differences. Next, explore over-fetching, then under-fetching in detail.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro