06. REST, gRPC, and GraphQL Protocols — three ways to pack the same message¶
~15 min read. Same network underneath, but very different application tradeoffs above it.
Built on the ELI5 in 00-eli5.md. The envelope — the packet carrying headers and payload — now shows why text, binary, and client-shaped APIs behave differently.
1) The internet stays same. The application envelope changes.¶
TCP, TLS, and routing stay underneath. What changes is the message format and conversation pattern. REST, gRPC, and GraphQL all sit at application layer. They answer three different questions. - How should the client ask? - How much data should the server send back? - Can one connection carry many conversations efficiently? See the stack first. ┌───────────────┐ │ App protocol │ REST / gRPC / GraphQL ├───────────────┤ │ HTTP family │ HTTP/1.1 or HTTP/2 ├───────────────┤ │ TLS optional │ HTTPS usually ├───────────────┤ │ TCP │ reliable delivery ├───────────────┤ │ IP │ routing across network └───────────────┘ So what is the real difference? The envelope shape and transport habits differ. REST usually sends JSON text. gRPC usually sends protobuf binary frames on HTTP/2. GraphQL usually sends one flexible query over HTTP. Network impact follows from that choice. Text payloads are larger, but easier to inspect. Binary payloads are smaller, but harder to eyeball. Flexible queries reduce round trips, but can hide heavy fan-out. Simple, no?
2) REST over HTTP and JSON¶
REST is the familiar web shape.
Client talks in resources, verbs, status codes, and JSON bodies.
Example request:
- GET /orders/417
- Host: api.shop.in
- Accept: application/json
Example response body:
- {"id":417,"status":"packed","items":3,"amount":1299}
The good part is clarity.
Every proxy, browser, log pipeline, and engineer understands it.
The cost is payload size.
Field names travel inside the body as text.
Numbers often travel as longer strings than necessary.
A worked size example helps.
Suppose one order response contains these fields.
- id = 417
- status = packed
- amount = 1299
- item_count = 3
- warehouse = blr-2
JSON body size is about 96 bytes.
Add HTTP headers of 320 bytes.
Total response size becomes roughly 416 bytes.
At 8,000 requests per second:
- bytes each second = 416 × 8,000 = 3,328,000 bytes
- megabytes each second ≈ 3.33 MB
- gigabytes each hour ≈ 3.33 × 3,600 / 1,000 = 11.99 GB
That is not scary for one endpoint.
It becomes expensive for many hot endpoints.
Now connection reuse.
REST does not mean one TCP connection per request.
With keep-alive, one connection can carry many sequential requests.
With HTTP/2, many REST requests can multiplex concurrently too.
But many REST deployments still behave like simple request-response traffic.
Streaming support exists, but is not the natural first fit.
Think of REST as clearly labeled text envelopes.
Easy to open.
Easy to debug.
Bigger on the wire.
Here is the picture.
┌──────────┐ HTTPS ┌─────────────┐
│ Mobile │ ─────────────→ │ REST API │
│ client │ ←───────────── │ JSON reply │
└──────────┘ └─────────────┘
Best fits:
- public APIs
- browser and mobile backends
- partner integrations
- systems where inspectability matters more than absolute efficiency
3) gRPC over HTTP/2 and protobuf¶
gRPC is stricter and leaner.
You define schema in .proto files.
Clients and servers generate typed stubs.
Payloads are protobuf binary, not JSON text.
Transport is usually HTTP/2.
That gives three useful network wins.
- smaller payloads
- one connection reused heavily
- built-in streaming styles
Let us reuse the same order response.
Protobuf may encode that body in about 34 bytes.
Add framing and headers of about 70 bytes.
Total becomes roughly 104 bytes.
Compare with REST example at 416 bytes.
Savings per response = 416 - 104 = 312 bytes.
At 8,000 requests per second:
- saved bytes each second = 312 × 8,000 = 2,496,000 bytes
- saved megabytes each second ≈ 2.50 MB
- saved gigabytes each hour ≈ 9.0 GB
See the difference.
Small bodies matter when call volume is huge.
Now the connection story.
HTTP/2 multiplexes many streams on one TCP connection.
Client need not open twenty separate sockets for twenty in-flight calls.
That reduces handshake overhead and connection churn.
Diagram time.
┌────────────┐ one HTTP/2 connection ┌──────────────┐
│ User Svc │ ═════════════════════════════════════→ │ Order Svc │
└────────────┘ stream 1 stream 2 stream 3 └──────────────┘
getOrder listOrders updateOrder
Now streaming.
gRPC supports four call shapes naturally.
- unary: one request, one response
- server streaming: one request, many responses
- client streaming: many requests, one response
- bidirectional streaming: both sides send continuously
Suppose one dashboard needs 50 order updates per second.
REST polling every second means 1 request each second.
gRPC server stream means one request, then 50 pushed messages.
Network control gets simpler.
Latency between updates also drops.
The tradeoff is operational friendliness.
Browsers do not speak raw gRPC as comfortably as backend services.
Logs are less human-readable.
Packet inspection needs better tooling.
Best fits:
- service-to-service calls inside one platform
- high QPS internal traffic
- typed contracts across many teams
- streaming workloads with stable clients
4) GraphQL and the client-shaped response¶
GraphQL changes a different thing.
It changes how the client asks for fields.
Usually there is one endpoint.
The request body describes exactly what fields are needed.
Example screen needs:
- user name
- last 5 orders
- loyalty points
REST may fetch from three endpoints.
- /users/9
- /users/9/orders?limit=5
- /users/9/loyalty
GraphQL may fetch in one call.
That reduces round trips over slow networks.
Here is the shape.
┌────────────┐ POST /graphql ┌──────────────┐
│ Web app │ ───────────────────→ │ GraphQL API │
│ asks fields│ ←─────────────────── │ one response │
└────────────┘ └──────┬───────┘
│
┌─────────────────────┼─────────────────────┐
▼ ▼ ▼
User service Orders service Loyalty service
Now the good part.
Client avoids overfetching and underfetching.
It asks for exactly 7 fields, not 40 fields.
Now the hidden cost.
Server may fan out to many internal services.
One neat query can trigger many backend calls.
Worked example:
Suppose one product page needs 12 fields.
REST path needs 4 calls of 140 ms each.
Mobile radio adds 60 ms round-trip delay per call.
Total client wait ≈ 4 × 60 + backend time = 240 + 140 = 380 ms.
GraphQL path needs 1 call.
Same mobile delay = 60 ms.
Aggregator work becomes 210 ms.
Total wait ≈ 270 ms.
Client wins by about 110 ms.
But if GraphQL resolver fans out badly,
backend cost may jump higher than 210 ms.
So GraphQL shifts optimization pressure to the server side.
Streaming support is not native in the same strong way as gRPC.
Subscriptions exist, but deployment patterns vary.
Best fits:
- frontend teams building many screen variants
- read-heavy aggregation use cases
- mobile clients where round trips hurt sharply
- platforms that can invest in query cost controls
5) Choose by network behaviour, not fashion¶
Put the tradeoff table in your head. ┌──────────┬──────────────┬────────────────┬────────────────────┐ │ Protocol │ Payload size │ Connection use │ Streaming support │ ├──────────┼──────────────┼────────────────┼────────────────────┤ │ REST │ larger │ okay to good │ possible, limited │ │ gRPC │ smaller │ excellent │ excellent │ │ GraphQL │ variable │ okay to good │ variable │ └──────────┴──────────────┴────────────────┴────────────────────┘ Ask four questions. - Is the client public or internal? - Are payload bytes expensive at this scale? - Do we need streaming? - Do clients need flexible field selection? One easy cheat sheet. Choose REST when simplicity and broad compatibility dominate. Choose gRPC when internal efficiency and streaming dominate. Choose GraphQL when frontend flexibility and field control dominate. In many real systems, all three coexist. Public API may stay REST. Internal services may use gRPC. Web product aggregation may expose GraphQL. That is not confusion. That is correct layering.
Where this lives in the wild¶
- Stripe — API platform engineer keeps public payment APIs simple with REST and JSON because partner compatibility matters deeply.
- Uber — backend engineer often prefers gRPC-style internal RPC because high-volume service calls reward binary payloads and multiplexed connections.
- GitHub — frontend platform engineer uses GraphQL so repository pages fetch exact fields without many browser round trips.
- Google Ads — infrastructure engineer benefits from typed internal APIs where payload efficiency and generated clients reduce call overhead.
- Swiggy — mobile backend engineer may mix REST for app clients, GraphQL for flexible views, and gRPC for internal service chatter.
Pause and recall¶
- What stays same underneath REST, gRPC, and GraphQL?
- Why can gRPC send smaller payloads than REST for same data?
- When does GraphQL reduce latency, and when can it hide backend pain?
- Which protocol gives the cleanest built-in streaming story?
Interview Q&A¶
Q: Why choose REST for a public API even when gRPC is more efficient? A: Public consumers care about compatibility, debuggability, and broad tooling. REST over HTTP with JSON works across browsers, SDKs, proxies, and partner stacks with far less friction. Common wrong answer to avoid: "Because REST is always faster" — it is usually friendlier, not inherently leaner on the wire. Q: Why choose gRPC for internal microservice calls? A: High call volume rewards smaller protobuf payloads, one reused HTTP/2 connection, and strong typed contracts. Those gains compound when many services talk continuously. Common wrong answer to avoid: "Because JSON cannot scale" — JSON scales fine often; the question is efficiency and control at your traffic level. Q: Why can GraphQL improve client performance but still hurt the backend? A: One query can reduce network round trips for the client, especially on mobile links. But resolvers may fan out into many internal calls unless batching and limits are designed well. Common wrong answer to avoid: "Because GraphQL sends less data in every case" — response shape may shrink, but server work can expand. Q: Why is streaming a protocol choice, not only an API design choice? A: The transport and framing matter. gRPC gives strong streaming patterns directly, while REST and GraphQL need more deliberate choices around polling, chunking, or subscription transports. Common wrong answer to avoid: "Any protocol can stream equally" — possible is not same as natural, efficient, and well-supported.
Apply now (5 min)¶
Exercise: You need a product page showing profile, cart, and recommendation data. Estimate one REST design with 3 calls of 120 ms each. Estimate one GraphQL design with 1 call of 210 ms. Then compare user wait time on a network with 50 ms round-trip delay. Next, take one internal service response of 400 bytes in JSON. Assume protobuf can send same information in 140 bytes. Compute saved bytes at 12,000 requests per second. Sketch from memory: Draw one stack with TCP, TLS, HTTP, and app protocol. Then draw three boxes labeled REST, gRPC, and GraphQL. Under each, write payload size, connection reuse, and streaming support.
Bridge. Application protocols decide message shape. Next we ask who should receive those messages when many servers stand behind one address. → 07-load-balancing-at-network-level.md