Tags: web-dev concept

REST GraphQL and RPC

Date: 2026-08-17


Three answers to “who decides the shape of the response”. REST says the server, GraphQL says the client, RPC says neither — there’s a function and it has a signature. The choice mostly follows from how many different clients you have and how much you trust them.


The same request, three ways

Fetching an order with its line items and the customer’s name.

REST — the server defined an /orders/:id representation, and you get that representation.

GET /orders/1234
GET /orders/1234/items        ← probably a second call
GET /customers/99             ← and a third, unless the server embeds them

GraphQL — one endpoint, and the query states exactly the fields wanted.

query {
  order(id: "1234") {
    total
    items { sku quantity }
    customer { name }
  }
}

RPC — a named procedure with typed arguments, usually generated from a schema.

orderService.GetOrder({ order_id: "1234", include_items: true })

What each optimises for

RESTGraphQLRPC (gRPC, tRPC)
Response shape decided byServerClientServer, per procedure
EndpointsManyOneMany procedures
Over-fetchingCommonSolvedRare — procedures are specific
Round tripsOften severalOneOne per procedure
HTTP cachingNative and freeEffectively lost — POST to one URLLost — binary, POST
Discoverable by handcurl, a browserGraphiQL, introspectionNeeds the schema and tooling
Typed end to endVia OpenAPI, bolted onYes, built inYes, built in
Cost modelPredictable per endpointUnpredictable — the client writes the queryPredictable
Best whenPublic API, many unknown consumers, cacheable readsMany clients each wanting different shapesInternal service-to-service, one org owns both ends

The thing each gets wrong

REST over-fetches and under-fetches, and the usual fix is a per-screen endpoint, which is a Backend for Frontend growing inside your public API. It’s also the only one where HTTP caching works for free — a GET with an ETag is cacheable by the browser, the CDN and every proxy between, which is a large advantage to give up — HTTP Caching, CDN Caching.

GraphQL moves the cost model to the client. A nested query can be cheap or can join four tables per row, and the server can’t tell until it runs. Real deployments need query depth limits, complexity budgets, persisted queries (only pre-registered queries allowed) and DataLoader-style batching to avoid the N+1 problem — none of which are optional at scale, and all of which are work that REST didn’t require. Errors are also awkward: a partial failure returns 200 OK with an errors array, so every monitoring tool that watches status codes sees success.

RPC assumes both ends are yours. Regenerating clients on a schema change is fine internally and impossible for a public API. gRPC’s binary framing also doesn’t survive a browser without a proxy layer.

Choosing

  • Public API, third parties, cacheable reads → REST. Predictable, debuggable with curl, and the caching is worth a lot — API Design
  • One backend, several clients that each want different fields → GraphQL, with persisted queries. This is the case it was invented for
  • Service to service, inside one organisation → RPC. Typed, fast, and the schema is enforced at build time
  • A web app whose backend is the same codebase → RPC-shaped (tRPC or server functions) is usually less ceremony than either alternative

They coexist. A common commerce shape is gRPC between services, a GraphQL or BFF layer for first-party clients, and REST for the public and partner API. Picking one for the whole estate is a preference, not a requirement.

Where it interacts

  • API Design — errors, pagination and versioning still need deciding in all three; only the mechanism changes
  • Rate Limiting — request counting is meaningless for GraphQL, where one request can cost anything. Limit on computed complexity instead
  • Rendering and SEO — a GraphQL-only storefront pushes data fetching into the client unless the server does it, which is a rendering decision before it’s an API one