Skip to content

API Styles: REST vs GraphQL vs gRPC

CoreConceptJuly 4, 20265 min readUpdated July 17, 2026

Your mobile team wants one round trip for a screen. Your partner wants a stable URL they can cache. Your services need typed calls inside the mesh. Those are three different API jobs — and picking one style for all of them usually hurts someone.

This article is the style comparison: REST, GraphQL, and gRPC as contract shapes, not audience types. You will see a short skimmer, one representative call per style (with failure shape), and a decision rule by caller. For who may call (Open / Internal / Partner), see Types of APIs. For URL and error conventions, see REST design practices.

API styles compared: REST vs GraphQL vs gRPC
API styles compared: REST vs GraphQL vs gRPC

Skimmer: which style fits?

REST maps resources to URLs and HTTP verbs — cache-friendly, browser-native, default for public HTTP APIs. GraphQL exposes a query language over one endpoint — clients ask for fields, servers resolve graphs. gRPC is an RPC framework over HTTP/2 with Protocol Buffers — typed stubs, streaming, usually internal.

Decision order: browser or third-party HTTP clients → start REST. Many client screens with divergent shapes from one backend → consider GraphQL. Service-to-service under your control, streaming or high call volume → gRPC. Most production systems layer them: REST or GraphQL at the edge, gRPC behind the gateway.

Quick reference

  • REST — resources + HTTP; OpenAPI; CDN caching; over/under-fetch risk.
  • GraphQL — one endpoint; client-shaped payloads; N+1 and caching cost.
  • gRPC — .proto contracts; binary payloads; not browser-native without a proxy.
  • Audience (public vs partner) is a separate axis — see Types of APIs.
  • Do not pick a style for "performance" without naming the workload.

Remember this

REST, GraphQL, and gRPC differ in contract shape, not raw speed — pick by who's calling and from where, not by an unscoped performance claim.

REST over HTTP

REST maps resources to URLs and uses HTTP verbs for actions. GET /users/42 returns a user; POST /orders creates an order. Responses are usually JSON — readable, cacheable with standard headers, and supported by browsers, mobile SDKs, and API gateways.

REST shines when the API is public, clients are diverse, and resources map cleanly to nouns. Version via URL (/v1/) or headers; document with OpenAPI. The weakness is over-fetching and under-fetching — a mobile screen needing user + posts + notifications may need several HTTP calls. Auth belongs in headers; see REST authentication methods.

REST: multiple endpoints, fixed responses
REST: multiple endpoints, fixed responses

Quick reference

  • Best for: public APIs, web apps, third-party integrations, CRUD.
  • Transport: HTTP with JSON — human-readable and widely supported.
  • Cache-friendly: ETag, Cache-Control, CDN edges on safe GETs.
  • Weakness: multiple round trips when a screen needs a graph of resources.
REST — successful resource fetch
1// GET /v1/users/422const res = await fetch("https://api.example.com/v1/users/42", {3  headers: { Authorization: `Bearer ${accessToken}` },4});5if (res.status === 401) {6  // { "error": { "code": "unauthorized", "message": "Invalid or expired token" } }7  throw new Error("re-authenticate");8}9if (res.status === 404) {10  // { "error": { "code": "not_found", "message": "User 42 not found" } }11  throw new Error("missing user");12}13if (!res.ok) throw new Error(`users ${res.status}`);14const user = await res.json(); // { id, name, email }
REST — create with status shape
1// POST /v1/orders → 201 Created + Location2const res = await fetch("https://api.example.com/v1/orders", {3  method: "POST",4  headers: {5    Authorization: `Bearer ${accessToken}`,6    "Content-Type": "application/json",7  },8  body: JSON.stringify({ sku: "sku_9", qty: 1 }),9});10if (res.status === 422) {11  // { "error": { "code": "validation_failed", "fields": { "qty": "must be >= 1" } } }12  throw new Error("fix body");13}14if (!res.ok) throw new Error(`create order ${res.status}`);15// 201 → Location: /v1/orders/ord_12316const order = await res.json();

Remember this

REST is the default client-facing style — simple, universal, and cache-friendly.

GraphQL

GraphQL replaces many REST endpoints with a single endpoint (often POST /graphql). The client sends a query for exact fields; the server resolves each field, often by calling internal services or databases. One request can return a user, posts, and notification counts shaped for the screen.

That cuts over-fetching and mobile bandwidth. The cost is server complexity: N+1 resolvers, query depth/cost limits, harder HTTP caching, and DataLoader-style batching. GraphQL fits when web, iOS, and Android need different shapes from the same product graph — not when every client needs the same CRUD resource.

GraphQL: one query, shaped response
GraphQL: one query, shaped response

Quick reference

  • Best for: complex frontends and dashboards with varied field needs.
  • Single endpoint — clients POST queries instead of many REST URLs.
  • Enforce depth/cost limits and auth per field — a flexible query is an attack surface.
  • Weakness: harder CDN caching than resource GETs; higher server complexity.
GraphQL — shaped query
1const query = `query ($id: ID!) {2  user(id: $id) {3    id4    name5    posts(limit: 3) { id title }6  }7}`;8const res = await fetch("https://api.example.com/graphql", {9  method: "POST",10  headers: {11    "Content-Type": "application/json",12    Authorization: `Bearer ${accessToken}`,13  },14  body: JSON.stringify({ query, variables: { id: "42" } }),15});16if (!res.ok) throw new Error(`GraphQL HTTP ${res.status}`);17const payload = await res.json();18// Success shape: { data: { user: { … } } }19const user = payload.data?.user;
GraphQL — error shape (partial or failed)
1// GraphQL often returns HTTP 200 with errors in the body2const payload = await res.json();3if (payload.errors?.length) {4  // {5  //   "errors": [{6  //     "message": "Not authorized to read posts",7  //     "extensions": { "code": "FORBIDDEN" },8  //     "path": ["user", "posts"]9  //   }],10  //   "data": { "user": { "id": "42", "name": "Ada", "posts": null } }11  // }12  throw new Error(payload.errors[0].message);13}

Remember this

GraphQL when clients need flexible, shaped data — not as a default for simple CRUD.

gRPC

gRPC is a binary RPC framework commonly used over HTTP/2. Services define contracts in .proto files; generators produce typed client and server stubs. Payloads are Protocol Buffers — compact and fast to parse compared with typical JSON. Unary, server streaming, client streaming, and bidirectional streaming are supported.

gRPC fits internal service-to-service calls where both ends are under your control. Browsers cannot call native gRPC directly — use gRPC-Web behind a proxy (for example Envoy) or keep gRPC internal and expose REST/GraphQL at the edge. Debug with grpcurl or server reflection, not browser DevTools alone.

gRPC: typed binary RPC
gRPC: typed binary RPC

Quick reference

  • Best for: microservices and internal service-to-service communication.
  • Transport: HTTP/2 + Protocol Buffers — typed contracts and streaming.
  • Tools: gRPC, Protocol Buffers, HTTP/2, grpcurl, Envoy.
  • Weakness: not browser-native — proxy or keep internal-only.
gRPCProtocol BuffersHTTP/2grpcurlEnvoy
gRPC — .proto + unary call (sketch)
1// user.proto (contract)2// service UserService {3//   rpc GetUser (GetUserRequest) returns (User);4// }5// message GetUserRequest { string id = 1; }6// message User { string id = 1; string name = 2; }7 8// Node-ish client sketch after codegen9const user = await userClient.GetUser({ id: "42" });10// user = { id: "42", name: "Ada" }
gRPC — status / error shape
1// Failures use gRPC status codes, not HTTP status alone2try {3  await userClient.GetUser({ id: "missing" });4} catch (err) {5  // err.code === grpc.status.NOT_FOUND  // 56  // err.code === grpc.status.UNAUTHENTICATED // 167  // err.code === grpc.status.PERMISSION_DENIED // 78  // err.details — human-readable message9  // Map at the gateway: NOT_FOUND → HTTP 404, UNAUTHENTICATED → 40110  throw err;11}

Remember this

gRPC for internal typed calls and streams — keep browsers behind REST/GraphQL or gRPC-Web.

Which style should you use?

Most production systems use more than one. The gateway exposes REST or GraphQL to external clients; internal services often speak gRPC. GraphQL resolvers may call gRPC backends under the hood. Layer styles by boundary — do not force one protocol through every hop.

Start with REST for one client type and straightforward resources. Add GraphQL when mobile and web diverge on field shapes. Add gRPC when internal contracts and streaming matter more than human-readable payloads. Avoid hype comparisons that claim a universal winner; measure your call patterns, caching needs, and who owns both ends of the wire.

One mobile screen: REST overfetch vs GraphQL field pick
One mobile screen: REST overfetch vs GraphQL field pick

Quick reference

  • Public/browser API → REST (or GraphQL if multiple client shapes).
  • Internal service-to-service → gRPC (REST is fine for simple CRUD).
  • Mobile/web with divergent screens → GraphQL over many REST endpoints.
  • Combine: GraphQL or REST gateway → gRPC backends is a common pattern.
  • Auth and rate limits still apply at the edge regardless of style.

Remember this

Layer protocols — REST/GraphQL at the edge, gRPC internally when both ends are yours.

Key takeaway

REST is universal and cache-friendly. GraphQL is client-shaped and flexible. gRPC is typed and stream-capable for internal meshes. None replaces the others — they complement different callers and boundaries.

Practice (20 min): List three callers in your system (browser, partner HTTP client, internal service). Assign REST, GraphQL, or gRPC to each with one sentence of evidence. Then write the success and failure JSON (or gRPC status) for one call on each path.

Share:

Related Articles

Selecting the communication protocol between clients, API gateways, and internal microservices impacts API latency, payl

Read

A travel checkout may call a public weather API, your own booking API, and a partner airline API. All three could use RE

Read

A checkout call may hit REST at the gateway, a GraphQL BFF for the mobile screen, gRPC between order and inventory, and

Read

Keep learning

Follow a structured path or browse all courses to go deeper.