---
title: "REST vs. GraphQL vs. gRPC: The 'Restaurant Menu' Mental Model"
description: "Why does GraphQL exist if REST works fine? A mastery guide to API protocols, when to use each, and how gRPC changes the game for internal services."
pubDate: "2023-05-25"
lang: "en"
slug: "rest-vs-graphql-vs-grpc"
url: "https://hoangyell.com/rest-vs-graphql-vs-grpc/"
author: "HoangYell"
translation_vi: "https://hoangyell.com/vi/rest-vs-graphql-vs-grpc/"
categories: ["System Design"]
tags: ["system-design", "api", "rest", "graphql", "grpc"]
heroImage: "https://raw.githubusercontent.com/HoangGeek/store/main/webp/photo-1484480974693-6ca0a78fb36b.webp"
---

"Should we use GraphQL?"

The engineering team splits in half. One side says it's amazing. The other says REST is fine. Nobody can explain the difference without defaulting to "it depends."

This guide settles it.

This is the **Mastery Guide** to API Protocols. We'll use the "Restaurant Menu" model to understand REST (Fixed Menu), GraphQL (Custom Order), and gRPC (Walkie-Talkie between chefs).

***

## Part 1: Foundations (The Mental Model)

### REST = The Printed Menu

A **REST API** is like a restaurant with a **printed menu**. The kitchen decides what dishes exist. You pick from the list.

*   **Strength**: Simple, universal. Every language and every tool (Postman, curl) understands it, typically serialized over JSON or XML (see the [JSON vs YAML vs TOML vs XML comparison](https://yellorn.com/help/json-vs-yaml-vs-toml-vs-xml) for serialization trade-offs).
*   **Weakness**: You get exactly what's on the menu. If you only want the salad from the "Grilled Chicken Salad Combo," you still pay for the whole combo. (**Over-fetching**). Or the menu doesn't have exactly what you want; the waiter brings you three separate dishes. (**Under-fetching**).

### GraphQL = The Custom Order

**GraphQL** is like a restaurant where you tell the chef **exactly** what you want.

*   "I want the chicken breast, no sauce, extra rice, and the mango salad dressing."
*   The kitchen makes exactly that. One trip. Exactly what you asked for.
*   **Strength**: No over-fetching. No under-fetching. Perfect for mobile apps where bandwidth is limited.
*   **Weakness**: The kitchen is complex. You must manage a schema. Authorization is harder (is this user allowed to ask for `user.creditCard`?).

### gRPC = The Chef's Walkie-Talkie

**gRPC** is not for customers. It's for **kitchen-to-kitchen communication**.

*   Chef from the Cold Kitchen radios Chef from the Hot Kitchen: "Steak ready?"
*   Uses a **binary protocol** (not human-readable text) for maximum speed.
*   Strongly typed. The contract is defined in a `.proto` file.
*   **Strength**: Blazing fast (3x-10x JSON/REST). Bi-directional streaming possible over HTTP/2 multiplexing (explore our deep dive on [TCP 3-Way Handshake & HTTP/3 QUIC](/tcp-vs-udp-handshake/)).
*   **Weakness**: Not for browsers. The `.proto` contract requires shared tooling.

```markdy
scene type=venn width=1100 height=700
theme: light=doodle dark=nebula

service REST "REST (Public & Universal)"
service GraphQL "GraphQL (Dynamic Client Query)" focal=true
service gRPC "gRPC (High-Speed Binary RPC)"

beat compare_protocols "Protocol Domain Intersections" {
  show $nodes stagger=60ms
  glow GraphQL "Client precision & mobile bandwidth efficiency"
}

player:
    playback:
      loop true
    controls:
      speed true
      speeds "0.25"
      fullscreen true
      gif true
      theme true
    interaction:
      zoom true
      pan true
      click_to_play true
      double_click_to_reset true
      keyboard false
    chrome:
      badge true
      progress none
```

***

## Part 2: The Investigation (The Technical Differences)

### 1. The N+1 Problem (REST's Achilles Heel)

Imagine a UI that shows a list of Posts with their Author names.

```
# 1 request for posts
GET /posts          → [{ id: 1, author_id: 5 }, { id: 2, author_id: 8 }, ...]

# Then N requests for each author!
GET /users/5        → { name: "Alice" }
GET /users/8        → { name: "Bob" }
... (For 50 posts → 51 total API calls)
```

**GraphQL's Fix: One query, exactly the data needed.**

```graphql
query {
  posts {
    title
    author {
      name  # GraphQL resolves this in ONE roundtrip
    }
  }
}
```

### 2. gRPC: The Proto Contract

The `.proto` file is the DNA of gRPC - a shared language both client and server understand.

```protobuf
// payment.proto
syntax = "proto3";

service PaymentService {
  rpc ProcessPayment (PaymentRequest) returns (PaymentResponse);
  rpc StreamUpdates (PaymentRequest) returns (stream PaymentEvent);  // Streaming!
}

message PaymentRequest {
  string order_id = 1;
  double amount = 2;
}

message PaymentResponse {
  bool success = 1;
  string transaction_id = 2;
}
```

From this one file, `protoc` generates strongly-typed client and server code for Python, Go, Java, etc.

***

## Part 3: The Diagnosis (When to Use What)

| Use Case | Best Choice | Why |
|:---|:---|:---|
| **Public API** (third-party devs) | **REST** | Universal - any language, any tool. |
| **Mobile App** (bandwidth matters) | **GraphQL** | Fetch exactly what the screen needs. No more. |
| **Internal Microservices** | **gRPC** | Fast, type-safe, streaming support. |
| **Real-time data** (chat, live feed) | **GraphQL Subscriptions** or **gRPC Streaming** | Built-in streaming. |
| **Simple CRUD** | **REST** | Don't over-engineer. |
| **BFF (Backend for Frontend)** | **GraphQL** | Aggregates multiple services for one UI. |

***

## Part 4: The Resolution (Code Examples)

### 1. REST (Python/Django)

```python
# urls.py
path("posts/<int:pk>/", PostDetailView.as_view()),

# views.py
class PostDetailView(APIView):
    def get(self, request, pk):
        post = Post.objects.select_related("author").get(pk=pk)
        return Response(PostSerializer(post).data)
```

### 2. GraphQL (Python/Strawberry)

```python
import strawberry
from typing import List

@strawberry.type
class Author:
    name: str

@strawberry.type
class Post:
    title: str
    author: Author

@strawberry.type
class Query:
    @strawberry.field
    def posts(self) -> List[Post]:
        return Post.objects.select_related("author").all()

schema = strawberry.Schema(query=Query)
```

### 3. gRPC (Python)

```python
# server.py (Payment Service)
import grpc
import payment_pb2, payment_pb2_grpc

class PaymentServicer(payment_pb2_grpc.PaymentServiceServicer):
    def ProcessPayment(self, request, context):
        # request.order_id, request.amount are strongly typed!
        success = charge(request.amount)
        return payment_pb2.PaymentResponse(
            success=success,
            transaction_id="txn_123"
        )

# client.py (Order Service calling Payment Service)
channel = grpc.insecure_channel("payment-service:50051")
stub = payment_pb2_grpc.PaymentServiceStub(channel)
response = stub.ProcessPayment(
    payment_pb2.PaymentRequest(order_id="ord_456", amount=99.99)
)
```

***

## Final Mental Model

```
REST     -> The Printed Menu. Universal, predictable, sometimes wasteful.
GraphQL  -> The Custom Order. Precise, powerful, complex to secure.
gRPC     -> The Chef's Walkie-Talkie. Fast, typed, internal only.

Over-fetching  -> "I asked for a user but got their entire order history too."
Under-fetching -> "I need author names, but /posts only gives me author_id."
N+1 Problem    -> "50 posts = 51 API calls." (GraphQL or SQL JOIN solves this)
```

**Start with REST.** Move to GraphQL only when mobile clients complain about data size. Move to gRPC when internal service latency becomes a bottleneck.
