Skip to content

Data Fetching Differences — REST vs GraphQL

DodaTech Updated 2026-06-28 5 min read

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

Can REST return only specific fields?

Some REST APIs support ?fields=name,email but it is not standard. Each implementation is different.

Does GraphQL always use less data?

Not always. If the client requests deeply nested fields, GraphQL can return more data than a focused REST endpoint.

How does caching differ between the two?

REST caches at the URL level. GraphQL caches at the resolver or persisted query level.

Which is faster for simple data?

REST is usually faster for simple requests because GraphQL has query parsing overhead.

Can I use both in the same project?

Yes. Many projects use REST for simple CRUD and GraphQL for complex data requirements.

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