REST vs GraphQL vs gRPC
Published
Three styles for one job: letting one program ask another for data or to do something. The quickest way to feel the difference is to make the same request in all three. A food delivery app's order screen needs an order's status, the restaurant's name and the delivery partner's location.
The mobile order screen needs fields from three services: orders, restaurants and delivery partners. Over a 4G connection in a small town, each round trip costs 300 ms. How many round trips would your current API design need, and how would you get it to one?
GET /orders/8812
→ { "id": 8812, "status": "PICKED_UP", "restaurantId": 41, "partnerId": 907,
"items": [...], "address": {...}, "payment": {...}, "createdAt": "...", ... }
GET /restaurants/41
→ { "id": 41, "name": "Meghana Foods", "menu": [...200 items...], "hours": {...}, ... }
GET /partners/907/location
→ { "lat": 12.9352, "lng": 77.6245, "updatedAt": "..." }
# 3 round trips, and most of the bytes are fields the screen never shows.REST: resources and HTTP verbs#
REST models your system as resources with URLs (/orders/8812) and uses HTTP's own
verbs and status codes on them: GET reads, POST creates, PUT and PATCH update,
DELETE deletes, 404 means not found. Because it is plain HTTP, everything on the web
understands it: browsers, curl, CDNs, caches, proxies, API gateways, logging tools.
Its two classic pains are visible above. Over fetching: /restaurants/41 returns the
whole menu when the screen needs a name. Under fetching: one screen needs three calls,
each a round trip. Teams fix this with field selection (?fields=name), embedding
(?include=restaurant), or a dedicated endpoint per screen built by a backend for
frontend.
GraphQL: the client chooses the shape#
GraphQL, released by Facebook in 2015, exposes one endpoint and a typed schema of everything that can be asked for. The client sends a query naming exactly the fields and relations it wants, across what may be several backend services, and gets back JSON in that exact shape. Great when many clients (an Android app, an iOS app, a web app, a partner) each need a different slice of the same data, and the frontend team wants to move without waiting for new endpoints. GitHub's v4 API and Shopify's storefront API are GraphQL.
The costs are real:
- Caching is harder. Queries are usually
POSTs to one URL, so HTTP and CDN caching do not work out of the box. Persisted queries (sending a hash of a known query viaGET) win some of it back. - The N+1 problem. A query for 50 orders and each order's restaurant can make 1 query for orders plus 50 for restaurants, because each field has its own resolver. The fix is a batching layer like DataLoader that collects the 50 ids and makes one call.
- Expensive queries. A client can ask for deeply nested data that costs a fortune to compute. You need depth limits, query cost analysis and per client rate limits.
- Errors come back as 200 with an
errorsarray, so HTTP status based monitoring misses them.
gRPC: typed function calls over HTTP/2#
gRPC, open sourced by Google in 2015, makes a remote call look like a local function call.
You define services and messages in a .proto file; tooling generates client and server
code in Go, Java, Python, TypeScript and a dozen more languages. Messages are Protocol
Buffers, a compact binary format, usually several times smaller than the equivalent JSON
and faster to parse. It runs on HTTP/2, so it multiplexes calls and supports
streaming in either or both directions.
That makes it excellent between services you control: strict contracts that break at
compile time instead of in production, low overhead, deadlines that propagate across calls.
It is awkward from browsers, which cannot speak gRPC's HTTP/2 framing directly, so you need
gRPC-Web or Connect plus a proxy. And binary payloads are not readable with curl.
In a .proto file, string status = 1; means field number 1 is the status, on the wire,
for every client ever deployed. Never reuse or renumber a field. Add new fields with new
numbers and mark removed ones reserved. This rule is what lets old and new versions talk to
each other.
| Choice | What you gain | What you pay | Pick it when |
|---|---|---|---|
| REST | Universal, simple, HTTP caching and CDNs work, easy to debug with a browser or curl. | Over and under fetching, chatty clients, contracts held together by documentation (or OpenAPI). | Public APIs, simple CRUD services, anything third parties integrate with. |
| GraphQL | Clients get exactly what they need in one round trip, typed schema, frontends move fast. | Caching, N+1 resolvers, query cost control, a gateway or server layer to run. | Product APIs serving many different clients and screens over the same graph of data. |
| gRPC | Generated typed clients, compact binary messages, streaming, deadlines. | Browser support needs a proxy, binary is harder to debug, HTTP/2 needs L7 load balancing. | Internal service to service traffic, especially polyglot and high volume, and mobile to backend where you own both. |
Many large systems use all three: gRPC between internal services, a GraphQL or backend for frontend layer that assembles screens for apps, and REST for the public developer API.
- GraphQLThe query language and spec
- Apollo GraphQLPopular GraphQL server, client and federation
- gRgRPC + Protocol BuffersGoogle's RPC framework and wire format
- OpOpenAPIMachine readable REST contracts, generated clients
- GitHubOffers both a REST and a GraphQL API
- EnvoyProxies gRPC, translates gRPC-Web from browsers
In the interview#
Checkpoint#
1. What is the N+1 problem in GraphQL?
2. Why is gRPC awkward to call directly from a browser?
3. What does REST get almost for free that GraphQL has to work for?
- REST: resources + HTTP verbs. Universal, cacheable, over and under fetches.
- GraphQL: one endpoint, client picks fields. Watch N+1, query cost and caching.
- gRPC: protobuf over HTTP/2, generated clients, streaming. Internal traffic. Needs a proxy for browsers.
- Proto field numbers never change.
- Large systems mix all three.
REST models resources at URLs and uses HTTP verbs and status codes, so browsers, CDNs, gateways and curl all understand it; its weakness is over fetching and needing several round trips per screen. GraphQL exposes a typed schema on one endpoint and lets each client ask for exactly the fields it needs in one request, which suits many different clients and fast moving frontends, at the cost of harder caching, the N+1 resolver problem solved with batching, and the need to limit query cost. gRPC defines services in protobuf, generates typed clients, sends compact binary messages over HTTP/2 and supports streaming, which makes it ideal between internal services, but browsers need gRPC-Web and HTTP/2 needs layer 7 load balancing. I typically use REST for public APIs, gRPC internally, and GraphQL or a backend for frontend to assemble screens for apps.