Home
Backend from First Principles / Module 34 — Beyond REST — gRPC & GraphQL

Beyond REST — gRPC & GraphQL

Two alternatives to REST and when each one earns its place. Same problem, different trade-offs.


Why REST Isn't Always the Answer

REST is the default for good reason. It's simple, it works over HTTP, every language has libraries for it, browsers speak it natively, and you can debug it with curl.

But REST has rough edges that show up at scale:

Two alternatives have become serious contenders for specific use cases: gRPC for service-to-service communication, GraphQL for client-shaped data.


gRPC — Fast, Typed, Service-to-Service

gRPC is Google's open-source RPC framework. The pitch: write once in a .proto file, get auto-generated client and server code in any language, communicate via efficient binary over HTTP/2.

How it works:

1. Define your service in a Protocol Buffer (.proto) file:

Protobuf
syntax = "proto3";

service UserService {
  rpc GetUser(GetUserRequest) returns (User);
  rpc ListUsers(ListUsersRequest) returns (ListUsersResponse);
}

message GetUserRequest {
  string user_id = 1;
}

message User {
  string id = 1;
  string email = 2;
  string name = 3;
  int64 created_at = 4;
}

2. Run the protoc compiler. It generates client and server stubs in Go, Java, Python, Node, Rust, etc.

3. Implement the server in your language. Call the client from any other language. The wire format is identical.

Go
// Server (Go)
func (s *Server) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.User, error) {
  user, err := s.db.FindUser(req.UserId)
  if err != nil { return nil, err }
  return &pb.User{Id: user.ID, Email: user.Email, Name: user.Name}, nil
}

// Client (Python — same API, different language)
response = stub.GetUser(GetUserRequest(user_id="abc123"))
print(response.email)

What gRPC gives you:

Where gRPC wins:
• Service-to-service communication inside your data center — every request is faster
• High-throughput systems where JSON parsing overhead matters
• Polyglot teams — Go service calling Python service calling Java service
• Real-time streaming workflows (live updates, telemetry)

Where gRPC loses:
• Browser clients — gRPC doesn't run natively in browsers (you need gRPC-Web, an adapter)
• Public APIs — partners expect REST; gRPC has a learning curve
• Easy debugging — you can't curl a gRPC endpoint without tools (grpcurl)

Rule of thumb: use REST/JSON at the edge (browsers, public APIs); use gRPC inside your system.


GraphQL — The Client Asks For What It Wants

GraphQL is Facebook's answer to "different clients need different shapes of data." Instead of the server defining endpoints, the client writes a query specifying exactly what it wants.

A REST request:

Text
GET /api/users/123
{
  "id": "123",
  "name": "Alice",
  "email": "alice@example.com",
  "createdAt": "...",
  "updatedAt": "...",
  "lastLogin": "...",
  "settings": { ...30 fields... },
  "preferences": { ...20 fields... }
}

The same data, GraphQL:

Graphql
query {
  user(id: "123") {
    name
    email
  }
}

Returns:

JavaScript
{ "user": { "name": "Alice", "email": "alice@example.com" } }

Just what the client asked for. Nothing more.

The big mental shift: the server publishes a SCHEMA (types, fields, relationships) and a set of root QUERIES and MUTATIONS. Clients compose any query that's valid against the schema. There are no "endpoints" — there's one URL, and the request body is a query.

Graphql
type User {
  id: ID!
  name: String!
  email: String!
  posts: [Post!]!
  followers: [User!]!
}

type Post {
  id: ID!
  title: String!
  body: String!
  author: User!
}

type Query {
  user(id: ID!): User
  posts(limit: Int): [Post!]!
}

A client can ask for a user, all their posts, and each post's author's name — in one request:

Graphql
query {
  user(id: "123") {
    name
    posts {
      title
      author { name }
    }
  }
}

What you get:

What it costs:

When GraphQL wins:
• Multiple clients (web, iOS, Android) with different data needs
• Aggregating data from many backend services into one client query
• Rapidly evolving products where the data model changes frequently

When GraphQL loses:
• Simple CRUD APIs — REST is simpler
• Public APIs — REST is more familiar to integrators
• High-throughput service-to-service — gRPC is faster


Picking Between REST, gRPC, and GraphQL

No protocol is universally best. The right choice depends on who's calling, what they need, and what your team can operate.

Text
                    ┌─────────────────────────────────────┐
                    │  Public API for partners/external?  │
                    │  → REST. Always.                    │
                    └─────────────────────────────────────┘

                    ┌─────────────────────────────────────┐
                    │  Browser/mobile client with         │
                    │  varied data needs?                 │
                    │  → GraphQL (or REST + BFF)          │
                    └─────────────────────────────────────┘

                    ┌─────────────────────────────────────┐
                    │  Service-to-service inside the      │
                    │  data center, high throughput?      │
                    │  → gRPC                             │
                    └─────────────────────────────────────┘

                    ┌─────────────────────────────────────┐
                    │  Simple CRUD service, small team,   │
                    │  no special needs?                  │
                    │  → REST. The defaults are good.    │
                    └─────────────────────────────────────┘

The real-world hybrid: large systems often use ALL THREE. REST at the edge for partners. GraphQL via a BFF layer for first-party clients. gRPC for service-to-service inside the cluster. Each protocol does what it's best at, and the boundaries between them are clear.

Don't try to pick one for everything. Pick the right tool for each boundary.


⁂ Back to all modules