Title: System Design Adventure - 5

Subtitle: REST vs GraphQL vs gRPC: Choosing the Right API

Posted by Afsal on 24-Jul-2026

Hi Pythonistas!

You have a server.

It has data.

A client wants that data.

How should they communicate?

Not the transport layer.

Not the network layer.

The application layer.

What shape should the request take?

What shape should the response take?

Three answers dominate the industry today.

REST. GraphQL. gRPC.

Let's understand all three.

Then I'll tell you which one to actually use.

REST

Representational State Transfer.

The most widely used API style in the world.

Built on top of HTTP.

Uses everything from Post 1.

Methods. URLs. Status codes. Headers.

The Core Idea

Everything is a resource.

A resource = a thing in your system.

User

Post

Comment

Order

Product

Every resource has a URL.

/users

/users/123

/posts/456

/posts/456/comments

You interact with resources using HTTP methods:

GET    /users       → get all users

GET    /users/123   → get user 123

POST   /users       → create a new user

PUT    /users/123   → replace user 123

PATCH  /users/123   → update part of user 123

DELETE /users/123   → delete user 123

Request:

GET /users/123

Authorization: Bearer xyz123

Accept: application/json

Response:

{

  "id": 123,

  "name": "Afsal",

  "email": "afsal@parseltongue.co.in",

  "created_at": "2024-01-15"

}

Clean. Simple. Everyone understands it.

REST  - Important Constraints

Stateless

Every request contains all information needed.

Server doesn't remember previous requests.

Request 1: GET /users/123  + Authorization: Bearer xyz

Request 2: GET /posts/456  + Authorization: Bearer xyz

Auth token sent every time.

Server stores nothing between requests.

Uniform interface

Same URL structure everywhere.

Anyone who knows REST understands your API immediately.

Client-server separation

Client doesn't care how server stores data.

Server doesn't care what client does with the response.

Independent. Replaceable.

REST Problems

Over-fetching

You want just the user's name.

REST gives you everything:

{

  "id": 123,

  "name": "Afsal",

  "email": "afsal@parseltongue.co.in",

  "phone": "9876543210",

  "address": "Kerala, India",

  "created_at": "2024-01-15",

  "last_login": "2024-06-01",

  "subscription": "pro"

}

You needed 1 field.

You got 8.

Wasted bandwidth.

Under-fetching

You want user + their posts + comments on each post.

REST requires:

GET /users/123

GET /posts?user_id=123

GET /comments?post_id=456

GET /comments?post_id=789

Four requests.

Four round trips.

Four times the latency.

This is called the N+1 problem.

GraphQL

Facebook built GraphQL in 2012.

Open-sourced in 2015.

Solves REST's over-fetching and under-fetching.

The Core Idea

Instead of the server deciding what to return 

the client asks for exactly what it needs.

One endpoint:

POST /graphql

Client sends a query describing exactly what it wants.

Request:

query {

  user(id: 123) {

    name

    posts {

      title

      comments {

        text

        author {

          name

        }

      }

    }

  }

}

Response:

{

  "data": {

    "user": {

      "name": "Afsal",

      "posts": [

        {

          "title": "System Design Adventure",

          "comments": [

            {

              "text": "Great post!",

              "author": {

                "name": "Pythonista123"

              }

            }

          ]

        }

      ]

    }

  }

}

One request.

Got user + posts + comments + authors.

Exactly the fields asked for.

Nothing more. Nothing less.

GraphQL Operations

Three types:

Query - read data

query {

  user(id: 123) {

    name

    email

  }

}

Mutation - write data

mutation {

  createPost(title: "New Post", content: "Hello") {

    id

    title

  }

}

Subscription  - real-time updates

subscription {

  newComment(postId: 456) {

    text

    author {

      name

    }

  }

}

Client stays connected.

Server pushes updates automatically.

GraphQL Problems

Caching is harder

REST: cache by URL.

GET /users/123 → cache this response

GraphQL: everything is POST /graphql.

URL never changes.

Standard HTTP caching doesn't work.

Need custom solutions.

N+1 problem on server side

Client asks for 100 users + their posts.

Naively:

Query 1: get all users

For each user:

  Query N: get their posts

100 users = 101 database queries.

Need extra tooling to fix this.

More complexity.

gRPC

Google built gRPC in 2015.

Used heavily inside Google.

Increasingly popular in microservices.

The Core Idea

Instead of sending JSON text 

send binary data.

Instead of URL structures 

define functions you call remotely.

RPC = Remote Procedure Call.

You call a function.

It runs on a remote server.

Feels like calling a local function.

Protocol Buffers

gRPC uses protobuf to define data.

You write a .proto file:

service UserService {

  rpc GetUser (GetUserRequest) returns (User);

}

 

message GetUserRequest {

  int32 id = 1;

}

 

message User {

  int32 id = 1;

  string name = 2;

  string email = 3;

}

gRPC generates code from this.

In any language.

Python. Go. Java. Rust.

Python client:

stub = UserServiceStub(channel)

response = stub.GetUser(GetUserRequest(id=123))

print(response.name)

Looks like calling a local function.

Actually runs on a remote server.

Why Binary?

JSON is text:

json

{"id": 123, "name": "Afsal"}  → 30 bytes

Protobuf encodes same data as binary:

same data → 11 bytes

3-10x smaller.

5-10x faster to parse.

At 10,000 requests per second 

that difference matters enormously.

gRPC runs on HTTP/2

Supports full streaming:

Unary           → one request, one response

Server streaming → one request, many responses

Client streaming → many requests, one response

Bidirectional   → many requests, many responses

Perfect for live data feeds, real-time events.

gRPC Problems

Not human readable.

JSON you can read in your browser.

Protobuf binary  you can't.

Debugging is harder.

Not browser friendly.

Browsers can't natively call gRPC.

Fine for server-to-server.

Not great for public APIs.

Side by Side

          REST        GraphQL     gRPC

───────────────────────────────────────────

Format     JSON        JSON        Binary

Speed      Medium      Medium      Fast

Flexible   Fixed       Client picks Fixed

Browser    ✅          ✅          ❌

Caching    ✅ Easy     ❌ Hard     ❌ Hard

Learning   Easy        Medium      Hard

Best for   Public APIs Complex apps Microservices

When to Use Which

Starting a project?       → REST

Public API?               → REST

Simple CRUD?              → REST

Small team?               → REST

Mobile, complex data?     → GraphQL

Internal microservices?   → gRPC

Performance critical?     → gRPC

A Mistake We Made

I need to tell you something honest.

Early in a project 

we chose GraphQL.

Not because we needed it.

Because it was new and exciting.

Result:

Two weeks of setup

Complex caching problems

N+1 issues on the server

Entire team on a learning curve

Development slowed to a crawl

We didn't have a mobile app with bandwidth problems.

We didn't have complex data requirements.

We had simple CRUD.

GraphQL solved problems we didn't have.

REST would have taken one day to set up.

GraphQL took two weeks.

The lesson:

Don't choose a tool because it's new.

Choose it because you feel a real pain it solves.

This is the system design mindset.

Don't solve problems you don't have yet.

What Most Companies Actually Do

Start with REST.

Everything is REST.

System grows.

Specific pain points appear.

Then:

REST       → 90% of the system

gRPC       → hot internal paths

GraphQL    → mobile or complex clients

Not a competition.

Different tools. Different jobs.

Mental Model

REST        → resources + HTTP methods, simple, universal

GraphQL     → client asks exactly what it needs

gRPC        → binary RPC, fast, HTTP/2, microservices

Protobuf    → binary format, smaller and faster than JSON

Over-fetch  → REST returning more data than needed

Under-fetch → REST requiring multiple requests

N+1         → fetching related data one by one

Stateless   → server remembers nothing between requests

Streaming   → gRPC sending multiple responses over one connection

What Changed for Me

Before this:

I chose tools based on what was trending.

After this:

I choose tools based on the problem I actually have.

REST is not old.

REST is not boring.

REST is the right answer most of the time.

And that's not a weakness.

That's engineering judgment.

What's Coming Next

Now you know:

Post 1  → HTTP/HTTPS

Post 2A → TCP/UDP

Post 2B → SSH

Post 3  → DNS

Post 4  → IP/Subnets

Post 5  → REST vs GraphQL vs gRPC

That's Phase 1 complete.

You now understand how the internet works.

How data travels.

How systems talk to each other.

Next Phase 2: Scalability.

Your app works for 100 users.

What happens when 1 million show up?

Vertical vs Horizontal Scaling.

The first big decision every growing system faces.

See you in Post 6.