Skip to content

Hybrid Approach — REST and GraphQL Together

DodaTech Updated 2026-06-28 6 min read

In this tutorial, you'll learn about the Hybrid Approach. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.

A hybrid approach runs both REST and GraphQL APIs, routing each request to the most appropriate style. REST handles simple CRUD and public endpoints, GraphQL powers complex internal UIs.

What You'll Learn

By the end of this lesson, you will design a hybrid API architecture, use GraphQL to wrap REST endpoints, and implement use-case-based routing.

Why It Matters

Choosing between REST and GraphQL is not binary. Many successful companies run both. A hybrid approach lets you use each tool where it excels.

Real-World Use

Shopify runs GraphQL for admin and REST for storefront. Netflix uses GraphQL for client apps and REST for service-to-service communication.

Hybrid Architecture

flowchart TD
    Client[Client Apps] --> Router[API Router]
    Router -->|Simple CRUD| REST[REST Endpoints]
    Router -->|Complex Queries| GQL[GraphQL Endpoint]
    REST --> Services[Backend Services]
    GQL --> Services
    GQL --> RESTWrapper[GraphQL wraps REST]

GraphQL Wrapping REST

# graphql_wraps_rest.py
from typing import Any, Dict

class RESTClient:
    def get_user(self, user_id: int) -> Dict:
        return {"id": user_id, "name": "Alice", "email": "a@x.com"}

    def get_orders(self, user_id: int) -> Dict:
        return {"orders": [{"id": 101, "total": 50}]}

class GraphQLWrapper:
    def __init__(self):
        self.rest = RESTClient()

    def resolve_user(self, user_id: int, fields: list) -> Dict:
        user = self.rest.get_user(user_id)
        return {f: user[f] for f in fields if f in user}

    def resolve_user_with_orders(self, user_id: int, user_fields: list,
                                  order_fields: list) -> Dict:
        user = self.resolve_user(user_id, user_fields)
        orders = self.rest.get_orders(user_id)
        user["orders"] = [
            {f: o[f] for f in order_fields if f in o}
            for o in orders["orders"]
        ]
        return user

    def query(self, gql_query: str) -> Dict:
        # Simplified query parser
        if "orders" in gql_query:
            return {"data": self.resolve_user_with_orders(1, ["name", "email"], ["id", "total"])}
        return {"data": self.resolve_user(1, ["name", "email"])}

wrapper = GraphQLWrapper()

rest_client = RESTClient()
print(f"REST: {rest_client.get_user(1)}")
print(f"REST: {rest_client.get_orders(1)}")

gql = wrapper.query("{ user(id: 1) { name email orders { id total } } }")
print(f"GraphQL (wrapping REST): {gql['data']}")

Expected output:

REST: {'id': 1, 'name': 'Alice', 'email': 'a@x.com'}
REST: {'orders': [{'id': 101, 'total': 50}]}
GraphQL (wrapping REST): {'name': 'Alice', 'email': 'a@x.com', 'orders': [{'id': 101, 'total': 50}]}

Use Case Router

# use_case_router.py
from typing import Any, Callable, Dict

class UseCaseRouter:
    def __init__(self):
        self.routes: Dict[str, callable] = {}
        self.gql_routes: Dict[str, callable] = {}

    def rest(self, pattern: str):
        def wrapper(fn):
            self.routes[pattern] = fn
            return fn
        return wrapper

    def graphql(self, query_type: str):
        def wrapper(fn):
            self.gql_routes[query_type] = fn
            return fn
        return wrapper

    def handle(self, method: str, path: str, body: Dict = None) -> Dict:
        if path.startswith("/graphql") and body and "query" in body:
            query = body["query"]
            for qtype, handler in self.gql_routes.items():
                if qtype in query:
                    return {"style": "graphql", "data": handler(body.get("variables", {}))}
            return {"error": "Unknown query"}

        handler = self.routes.get(path)
        if handler:
            return {"style": "rest", "data": handler()}

        return {"error": "Not found"}

router = UseCaseRouter()

@router.rest("/users")
def list_users():
    return [{"id": 1, "name": "Alice"}]

@router.rest("/users/1")
def get_user():
    return {"id": 1, "name": "Alice"}

@router.graphql("users")
def gql_users(variables):
    return [{"id": 1, "name": "Alice"}]

@router.graphql("user")
def gql_user(variables):
    return {"id": 1, "name": "Alice"}

print(router.handle("GET", "/users"))
print(router.handle("POST", "/graphql", {"query": "{ users { id name } }"}))
print(router.handle("GET", "/unknown"))

Expected output:

{'style': 'rest', 'data': [{'id': 1, 'name': 'Alice'}]}
{'style': 'graphql', 'data': [{'id': 1, 'name': 'Alice'}]}
{'error': 'Not found'}

Data Source Abstraction

# data_source.py
from typing import Any, Dict, List

class UserRepository:
    def __init__(self):
        self.users = {
            1: {"id": 1, "name": "Alice", "email": "a@x.com", "role": "admin"},
            2: {"id": 2, "name": "Bob", "email": "b@x.com", "role": "user"},
        }

    def get_by_id(self, user_id: int) -> Dict:
        return self.users.get(user_id, {})

    def list_all(self) -> List[Dict]:
        return list(self.users.values())

class RESTAdapter:
    def __init__(self, repo: UserRepository):
        self.repo = repo

    def handle(self, path: str) -> Dict:
        if path == "/users":
            return {"users": self.repo.list_all()}
        return {"user": self.repo.get_by_id(1)}

class GraphQLAdapter:
    def __init__(self, repo: UserRepository):
        self.repo = repo

    def resolve(self, fields: List[str], user_id: int = None) -> Dict:
        if user_id:
            user = self.repo.get_by_id(user_id)
            return {f: user.get(f) for f in fields}
        users = self.repo.list_all()
        return [{f: u.get(f) for f in fields} for u in users]

repo = UserRepository()
rest_adapter = RESTAdapter(repo)
gql_adapter = GraphQLAdapter(repo)

# Same data, different access patterns
print(f"REST:  {rest_adapter.handle('/users')}")
print(f"GraphQL: {gql_adapter.resolve(['id', 'name'], 1)}")

Expected output:

REST:  {'users': [{'id': 1, 'name': 'Alice', 'email': 'a@x.com', 'role': 'admin'}, {'id': 2, 'name': 'Bob', 'email': 'b@x.com', 'role': 'user'}]}
GraphQL: {'id': 1, 'name': 'Alice'}

Common Mistakes

1. Duplicating Business Logic

Maintaining separate REST and GraphQL handlers for the same logic doubles bugs. Share a service layer.

2. Inconsistent Security

REST and GraphQL must apply the same authentication and authorization. Different implementations can create security gaps.

3. Mixed Caching Strategies

If REST caches responses but GraphQL bypasses cache, users see inconsistent data. Coordinate caching at the data source level.

4. No Unified Documentation

Documenting REST separately from GraphQL confuses developers. Create a unified API portal.

5. Premature Hybrid Architecture

Starting with both is unnecessary complexity. Begin with REST or GraphQL, add the second when the need is proven.

Practice Questions

1. When should you use a hybrid REST/GraphQL approach?

When you have different client types with different needs, or when migrating from REST to GraphQL gradually.

2. How does GraphQL wrap REST endpoints?

GraphQL resolvers call REST endpoints internally, transforming the REST response into the requested GraphQL fields.

3. What is the main risk of a hybrid approach?

Inconsistency between REST and GraphQL for the same data. Both must use the same business logic.

4. How do you share logic between REST and GraphQL?

Extract business logic into a shared service layer that both REST handlers and GraphQL resolvers call.

Challenge

Design a hybrid API for an e-commerce platform: REST for product listing and checkout, GraphQL for the admin dashboard. Share a service layer between both.

FAQ

Can GraphQL call REST endpoints?

Yes. GraphQL resolvers can fetch data from any source, including REST APIs. This is a common migration pattern.

Does a hybrid approach double maintenance?

If you share business logic, no. The API layer (REST vs GraphQL) is thin. The service layer is shared.

Should I expose one endpoint or two?

Two: one REST endpoint and one GraphQL endpoint. Clients choose which to use.

How do I authenticate both APIs the same way?

Use the same authentication middleware for REST and GraphQL. Token-based auth works for both.

What companies use hybrid successfully?

Shopify, Netflix, GitHub (REST v3 + GraphQL v4), and many others run hybrid API architectures.

Mini Project: Hybrid API Gateway

# hybrid_gateway.py
from typing import Any, Callable, Dict

class HybridGateway:
    def __init__(self):
        self.rest_handlers: Dict[str, Callable] = {}
        self.gql_resolvers: Dict[str, Callable] = {}

    def rest(self, path: str, handler: Callable):
        self.rest_handlers[path] = handler

    def graphql(self, field: str, resolver: Callable):
        self.gql_resolvers[field] = resolver

    def process(self, request: Dict) -> Dict:
        if request.get("type") == "rest":
            handler = self.rest_handlers.get(request["path"])
            return {"from": "rest", "data": handler() if handler else "not found"}
        elif request.get("type") == "graphql":
            resolver = self.gql_resolvers.get(request.get("field"))
            return {"from": "graphql", "data": resolver() if resolver else "not found"}
        return {"error": "unknown"}

gw = HybridGateway()
gw.rest("/users", lambda: [{"id": 1}])
gw.graphql("users", lambda: [{"id": 1, "name": "Alice"}])

print(gw.process({"type": "rest", "path": "/users"}))
print(gw.process({"type": "graphql", "field": "users"}))

Expected output:

{'from': 'rest', 'data': [{'id': 1}]}
{'from': 'graphql', 'data': [{'id': 1, 'name': 'Alice'}]}

What's Next

You understand the hybrid approach. Finally, learn how to choose the right API style for your project.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro