Topic 18

REST, gRPC, GraphQL

Architecture

Three ways to expose a service, each built for a different caller. REST is for a public surface that many unknown clients will cache, browse and call from a shell. gRPC is for a typed, fast contract between services one team controls, compiled from one schema on both sides. GraphQL is for a client that needs to shape its own reads across many resources in one round trip, which is to say a user interface with many screens. Stagedoor is REST and stays REST, and this topic is the honest comparison of what each of the other two would have bought and cost, so the reader chooses next time on the caller and not on the year.

The comparison is not a survey. Each style is a set of tradeoffs that the previous six topics already touched: what a CDN can cache, where the method's promises go, what a schema is worth, where the N+1 problem lives, and how an error reaches the client. The style that wins on one axis loses on another, and the questions at the end of the topic are the ones that decide.

What Each Optimizes For

REST is resources over HTTP with the method's promises attached. Every proxy, cache, load balancer and browser already understands it, a request can be read with curl, and the seat map is cacheable by anything between the service and the buyer without the cache knowing what a seat is. gRPC is a .proto schema, binary Protobuf over HTTP/2, generated clients in every language, and streaming in both directions. It is built for a call from one service to another where both sides are compiled from the same contract and the contract is the test. GraphQL is one endpoint, a typed schema, and a client that writes its own query: the organizer's overview screen asks for the event, its sales total, its next three orders and their buyers in one request instead of six, and gets back exactly the fields it named.

Three styles, three callers
RESTmany unknown clients
HTTP semantics, cacheable by any proxy, readable with curl. Over-fetches, and has no schema unless you write the OpenAPI document.
gRPCservices you control
One schema compiled into both ends, binary, streaming. Opaque to browsers and CDNs, needs a proxy to reach a web page.
GraphQLone client, many screens
The client shapes each read. Every read is a POST to one URL, the N+1 problem moves to the server, and the query needs a cost limit.

What Each Gives Up

REST has no schema unless the team writes one, which is why Chapter 10 makes the OpenAPI document a required artifact and tests against it, and it over-fetches: the event page fetches the whole event to show its title. gRPC is opaque to browsers and to most proxies, so a CDN cannot cache a seat map that is a Protobuf frame on a multiplexed HTTP/2 stream, and a browser cannot call it at all without a translating proxy in front. GraphQL gives up the most HTTP: every read is a POST to one URL, so nothing between the client and the service can cache it and the method promises of Chapter 2 are gone. It moves the N+1 problem of Chapter 6 from the client to the server, because a query for twenty orders and their buyers is twenty buyer lookups unless the resolver batches them. And it needs a cost limit on every query, because a client can ask for every event, every order in each, every ticket in each order, in one request, and a service without a limit will try to answer it.

Where Stagedoor Would Use Each

The public API and the scanner are REST, because the clients are unknown, the seat map is cached at the CDN, and a venue's IT staff can debug a scanner problem with curl and a ticket code. A future call from the api to a separate pricing service would be gRPC, because both ends are Marek's, the schema is the contract and the contract is the test, and the call is inside the network where no browser or cache is involved. The organizer dashboard's overview screen is the one place GraphQL would pay: six REST calls per screen load, each fetching more than the screen shows.

The book then explains why one screen is not enough reason. A second API style is a second authentication path, a second error shape, a second versioning story and a second thing the on-call has to know at three in the morning. The overview screen's six calls became one when Marek added a purpose-built GET /events/{id}/overview that returns what the screen needs, which is a REST resource with a name that says what it is for. A read-shaping problem on one screen is solved with one endpoint. A read-shaping problem on forty screens, measured, is the day GraphQL earns its second stack.

The Streaming Question

gRPC streams natively, in either direction, on one HTTP/2 stream. REST has two answers, and the choice is about the client's environment. Server-sent events are a long-lived GET whose response body is a stream of events; a browser handles them with a built-in class, proxies pass them, and the seat-map live view during on-sale uses them to push "14C is now sold" to every open checkout page without a poll. WebSockets are two-way, for the case where the client also sends continuously, which the door scanner does not need and the seat map does not need either. A browser can do SSE and WebSockets and cannot do raw gRPC without a proxy, so a stream to a web page is one of the first two whatever the rest of the API is.

Errors and Versioning in Each

REST has the status codes of Chapter 2 and the Problem Details body of Topic 15, read by every generic client. gRPC has sixteen error codes of its own, with a rich error-details message that carries the same kind of typed extension, and the generated client raises the right exception on each. GraphQL returns 200 with an errors array beside the partial data, which is the 200-with-an-error that Chapter 2 warned against, made deliberate by the specification: a query can half succeed, and the client library is written to read the array. It works because the client library is always a GraphQL library; it is exactly the shape that broke the scanner when a plain HTTP library met it. Versioning follows the same lines: REST versions by the date header of Topic 17, gRPC by additive field numbers in the schema that must never be reused, and GraphQL by deprecating fields in the schema and never versioning the endpoint at all.

Choosing Without Fashion

Five questions decide it. Who calls: unknown clients, or ones the team compiles. Can they be updated: tonight, or eight months from now. Does caching matter: a seat map read 2,600 times a second, or a pricing call inside the network. Is the contract shared: one schema on both sides, or a document the client reads. Does the client need to shape reads: forty screens with different needs, or one overview. For a public API the answers point at REST almost every time, and the book says so instead of surveying. The wrong choice is not fatal. The wrong choice made for fashion, with a second style to maintain beside the first, is the cost that lasts.

Five questions, and where each answer points
Unknown clients, cannot be updated, caching mattersREST, with an OpenAPI document
Both ends yours, schema is the test, inside the networkgRPC
One client, many screens, read shaping measuredGraphQL, as the only style on that surface
One screen that needs six callsOne purpose-built REST resource
A stream to a browserSSE or WebSockets, never raw gRPC
REST vs gRPC vs GraphQL

REST: many unknown clients, HTTP semantics, caching by anything in the path. The default for a public surface and for anything a scanner or a shell will call.

gRPC: known clients, one shared schema, speed, streaming. The default for a call between two services one team compiles.

GraphQL: one client with many screens and a measured read-shaping problem. The wrong choice among the three is not fatal; the wrong choice made for fashion, with a second API style to maintain, is the cost.

Common Mistakes
  • GraphQL for a public API — every read is an uncacheable POST, the seat map cannot sit at the CDN, and one client's unbounded query is a denial of service the service wrote itself.
  • gRPC to a browser — a translating proxy layer, a second serialization, and debugging with curl gone; the venue's IT staff cannot read a scanner exchange any more.
  • REST between two services you control with no schema — the contract is a chat thread, the client is written from memory of the response, and the contract test of Chapter 10 is the fix REST needs and gRPC has built in.
  • Running two styles for one service — twice the authentication, twice the error shapes, twice the versioning, and an on-call who has to know both at three in the morning.
  • Treating GraphQL's 200-with-errors as a bug in the spec — it is the spec, and a plain HTTP library that reads the status alone will treat a half-failed query as success, which is the scanner bug of Chapter 2 in a new coat.
Best Practices
  • Default to REST for anything an unknown client will call, and write the OpenAPI document as part of the service, not after it.
  • Reach for gRPC when both ends are yours, the call is inside the network, and the schema is the test.
  • Reach for GraphQL only when a specific client's read-shaping problem is measured across many screens, and give it query-cost limits on day one.
  • Solve one screen's six calls with one purpose-built REST resource before adding a second API style.
  • Pick one style per surface and do not add a second without retiring the first.
Comparable toolsgRPC with Protobuf, Connect and grpc-web for reaching a browserGraphQL with Apollo, Strawberry (Python), HasuraOpenAPI REST's schema, Chapter 10tRPC typed RPC for a TypeScript monorepo, the same instinct as gRPC without the proto

Knowledge Check

The seat map is read 2,600 times a second at on-sale. Which style lets a CDN absorb those reads, and why do the other two not?

  • GraphQL, because one endpoint gives the CDN a single URL to cache; REST spreads the map over many
  • gRPC, because HTTP/2 multiplexing lets the CDN hold one stream open and serve every client from it
  • REST, because a GET on a URL is what a CDN keys on; GraphQL reads are POSTs and gRPC is opaque
  • Any of the three, because a CDN caches by response body hash regardless of the request's shape

Under GraphQL, where does the N+1 problem move to?

  • To the server's resolvers, which fetch each nested object one at a time
  • To the client, which must send one query per nested object it needs
  • To the CDN, which re-fetches each nested object on every cache miss
  • Nowhere, because a single typed query eliminates the repeated lookups entirely

Why does a browser need a proxy to call a gRPC service, when it can call REST directly?

  • Because gRPC requires mutual TLS, which a browser cannot present without a proxy holding the client certificate
  • Because browsers speak only HTTP/1.1, and gRPC runs on HTTP/2, so the proxy downgrades the protocol
  • Because the .proto schema must be compiled on the server side, and a browser cannot run the generator
  • Because gRPC uses HTTP/2 framing and trailers that the browser's fetch API does not expose to a page

The organizer's overview screen needs six REST calls. Why did Marek add one purpose-built REST resource instead of GraphQL?

  • Because a REST resource is faster than a GraphQL query, since it skips the query parser
  • Because one screen does not justify a second API style with its own auth, errors and versioning
  • Because GraphQL cannot express a read that spans events, orders and buyers in one query
  • Because the load balancer in front of api-01 and api-02 cannot route a GraphQL request

You got correct