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.