REST vs GraphQL vs gRPC

Three popular ways for services to talk. See how each works, compare them side by side, and learn which to pick for public APIs, mobile apps and internal microservices.

Intermediate⏱ 5 min readLesson 3 of 12#backend#api#graphql#grpc#rest

The big idea

Three ways to order food:

  • REST 🍱 = a set menu. Each dish (endpoint) comes as the kitchen designed it. Simple and predictable, but you might get extra sides you didn't want or need to order several dishes.
  • GraphQL 🥗 = a build-your-own salad bar. You list exactly the ingredients you want, and get them in one bowl.
  • gRPC 📞 = the kitchen's internal intercom. Super fast, strict shorthand between staff. Not meant for customers.

REST, GraphQL and gRPC comparedREST, GraphQL and gRPC compared

REST: resources over HTTP

GET /users/42
GET /users/42/posts?limit=3
GET /users/42/followers/count

Problems it can have:

  • Over-fetching: GET /users/42 returns 30 fields when the screen needs 2.
  • Under-fetching: the profile screen needs 3 round trips (user + posts + followers).
Drawing diagram…

GraphQL: ask for exactly what you need

One endpoint (POST /graphql). The client sends a query describing the exact shape it wants:

query ProfileScreen {
  user(id: 42) {
    name
    avatarUrl
    posts(limit: 3) { title likes }
    followers { totalCount }
  }
}
{
  "data": {
    "user": {
      "name": "Ana",
      "avatarUrl": "https://…/ana.png",
      "posts": [{ "title": "Hello GraphQL", "likes": 12 }],
      "followers": { "totalCount": 1284 }
    }
  }
}
Drawing diagram…

The server defines a schema (a typed contract) and resolvers (functions that fetch each field):

type User {
  id: ID!
  name: String!
  avatarUrl: String
  posts(limit: Int = 10): [Post!]!
}

type Query {
  user(id: ID!): User
}

type Mutation {
  createPost(title: String!, body: String!): Post!
}
const resolvers = {
  Query: { user: (_, { id }) => usersRepo.findById(id) },
  User: { posts: (user, { limit }) => postsRepo.byAuthor(user.id, limit) },
};

⚠️ The N+1 problem: listing 50 users and their posts can trigger 1 query for users + 50 queries for posts. Fix it with DataLoader, which batches those 50 calls into one WHERE author_id IN (...).

GraphQL challenges: HTTP caching is harder (everything is a POST to one URL), expensive queries need depth/complexity limits, and the server is more complex.

gRPC: fast, typed calls between services

gRPC (from Google) lets one service call a function on another as if it were local. Contracts are written in Protocol Buffers (.proto), and data travels as compact binary over HTTP/2.

syntax = "proto3";

service OrderService {
  rpc GetOrder (GetOrderRequest) returns (Order);
  rpc StreamOrderUpdates (GetOrderRequest) returns (stream OrderStatus); // server streaming
}

message GetOrderRequest { string order_id = 1; }

message Order {
  string id = 1;
  string customer_id = 2;
  double total = 3;
  repeated string items = 4;
}

From this file, tools generate client and server code in Go, Java, Node.js, Python…

// Node.js client (generated stubs make it feel like a local call)
const order = await orderClient.getOrder({ orderId: "ord_123" });

The four gRPC call types

Drawing diagram…

Why it's fast: binary payloads are 3–10× smaller than JSON, HTTP/2 multiplexes many calls on one connection, and nothing is parsed from text.

Limits: browsers can't call gRPC directly (you need gRPC-Web or a proxy), payloads aren't human-readable, and it needs tooling for debugging.

Side-by-side comparison

RESTGraphQLgRPC
StyleResources + HTTP verbsQuery language, one endpointRemote procedure calls
FormatJSON (text)JSON (text)Protobuf (binary)
TransportHTTP/1.1 or 2HTTPHTTP/2
ContractOpenAPI (optional)Schema (required).proto (required)
Data fetchedServer decidesClient decidesServer decides
HTTP caching✅ Easy (GET + CDN)❌ Harder❌ No
StreamingSSE / WebSockets separatelySubscriptions✅ Built in
Browser support✅ Native✅ Native⚠️ Needs gRPC-Web
PerformanceGoodGoodExcellent
Learning curveLowMediumMedium

Which should I choose?

Drawing diagram…

Many companies use all three: gRPC between internal microservices, a GraphQL or BFF (Backend for Frontend) layer for their apps, and REST for public partners.

Drawing diagram…

Key takeaways

  • REST: simple, cacheable, great for public APIs; can over- or under-fetch.
  • GraphQL: the client asks for exactly what it needs in one request; watch out for N+1 and query cost.
  • gRPC: binary, typed and streaming over HTTP/2; ideal between internal services.
  • Choose by who calls the API, not by trends. Mixing them is normal.