C.W.K.
Stream
Lesson 02 of 04 · published

When Not to REST — gRPC, GraphQL, and the Right-Tool Question

~10 min · epilogue, grpc, graphql, alternatives

Level 0HTTP Newbie
0 XP0/46 lessons0/12 achievements
0/120 XP to next level120 XP to go0% complete
"REST is one architectural style. It dominates because it's good enough for most cases and infrastructure-friendly. But three categories of work have better alternatives, and pretending otherwise is wasted effort."

The Cases Where REST Is Wrong

1. Typed internal service-to-service traffic → gRPC. When two services you control talk to each other in high volume, you want typed contracts, generated stubs (no hand-rolled clients), and HTTP/2 multiplexing without the JSON overhead. gRPC + Protobuf does this cleanly. Kubernetes, every major cloud provider's internal mesh, microservice estates — all gRPC inside.

2. Rich client picking fields from a deep schema → GraphQL. When you have many client types (web, mobile, partner apps) each wanting different slices of related data — products + reviews + author + similar products, but each client wants different subsets — REST endpoints proliferate or return too much. GraphQL lets clients write the query.

3. Naturally procedural operations → RPC-over-HTTP. Some operations don't naturally map to resources. "Compile this code," "transcode this video," "complete this prompt" — they're verbs, not nouns. POSTing to action endpoints with rich payloads (OpenAI's /v1/chat/completions) is honestly RPC. Pretending it's REST is forced.

gRPC — Typed, Binary, HTTP/2

gRPC defines services as Protobuf .proto files; tools generate typed clients and servers in 10+ languages. Wire format is binary Protobuf over HTTP/2. The wins:

  • Typed everything. No "is that field a string or number?" — the .proto says.
  • Generated stubs. Clients in Go, Python, Java, TypeScript — all from the same .proto, all type-checked.
  • HTTP/2 multiplexing built in. Many concurrent calls on one connection.
  • Bidirectional streaming. Either side can stream; way simpler than WebSocket framing.

The costs: binary wire is unreadable in browser DevTools; CDN support is limited (gRPC-Web is the workaround); proto evolution requires discipline. Worth it for internal service meshes; rarely worth it for public APIs where the consumers are diverse.

GraphQL — Client-Chosen Shape

GraphQL exposes a single endpoint (typically POST /graphql) where the client sends a query in GraphQL syntax describing exactly what fields it wants:

{
  user(id: "u_42") {
    name
    email
    orders(limit: 3) {
      id
      total
      items { name }
    }
  }
}

The server resolves each field independently. Clients get exactly what they asked for. The wins:

  • Over-fetch / under-fetch solved. Mobile gets a small payload; web admin gets a big one — same endpoint.
  • One round trip for related data. No N+1 client-side.
  • Strong typing via the GraphQL schema.

The costs: caching is harder (every query is different); a complex query can hammer the DB if you don't write resolvers carefully; harder for intermediaries (CDNs, proxies) to optimize because the request shape varies per call.

RPC — When the Operation Has No Noun

For genuinely procedural operations, RPC-over-HTTP (POST to an action endpoint with a JSON payload) is honest. POST /complete, POST /transcode, POST /sign. The OpenAI Chat Completions API is exactly this — and pretending it's REST would be a charade.

Modern RPC patterns: tRPC (TypeScript-only, typed end-to-end), JSON-RPC 2.0, plain POST-with-JSON. Picking RPC for the right operation is not 'giving up on REST'; it's matching protocol to traffic shape.

Architectural style is a choice, not a virtue. REST won 80% of API traffic because it fits 80% of traffic shapes — caching, intermediaries, browser-friendly, statelessly retryable. The 20% has better tools. Pick by traffic shape; defend the choice in two sentences; don't get philosophical about it.

The Decision Heuristics

  • Internal service mesh, both sides typed → gRPC.
  • Many diverse clients wanting different field subsets → GraphQL.
  • Operation-shaped traffic with no obvious noun → RPC over HTTP.
  • Public CRUD, integrations, browser apps → REST.
  • Real-time bidirectional → WebSocket (or gRPC streaming for internal).
  • Server-pushed events, one-way → SSE.

Most production systems use 2-3 of these. cwkPippa is REST + SSE + WebSocket. A typical SaaS is REST + GraphQL + gRPC (REST for public, GraphQL for the rich web app, gRPC internally). Hybrid is normal; mono-style is rare.

cwkPippa's Choices, Revisited

cwkPippa is REST + SSE + WebSocket. No gRPC (single service, no internal mesh worth the typed-contract overhead). No GraphQL (single web client, REST endpoints cover every shape it needs). RPC-flavored action endpoints (POST /api/council/{id}/finalize) where the operation isn't a resource. The mix is deliberate; trying to do everything in one style would be ideology over engineering.

Code

gRPC: typed service definition + multi-language stubs·protobuf
// gRPC service definition — users.proto
syntax = 'proto3';

package users.v1;

service UsersService {
  rpc ReadUser    (ReadUserRequest)   returns (User);
  rpc CreateUser  (CreateUserRequest) returns (User);
  rpc ListUsers   (ListUsersRequest)  returns (stream User);  // server streaming
  rpc Chat        (stream ChatMsg)    returns (stream ChatMsg);  // bidirectional streaming
}

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

message ReadUserRequest  { string id = 1; }
message CreateUserRequest { string name = 1; string email = 2; }
message ListUsersRequest { int32 limit = 1; }
message ChatMsg { string text = 1; }

// Generate clients in any language:
//   protoc --python_out=. --grpc_python_out=. users.proto
//   protoc --go_out=. --go-grpc_out=. users.proto
//   protoc --ts_out=. users.proto
// Same contract, every language; HTTP/2 multiplexed; bidi streaming built in.
GraphQL: typed schema + client-chosen query shape·graphql
# GraphQL query — client chooses the shape
query GetUserWithOrders($userId: ID!) {
  user(id: $userId) {
    name
    email
    orders(limit: 3, status: ACTIVE) {
      id
      total
      items {
        name
        price
      }
    }
  }
}

# Server resolves each field; client gets exactly what was asked.
# Mobile sends a smaller query; web admin sends a richer one. Same endpoint.

# Schema (server-defined)
type User {
  id: ID!
  name: String!
  email: String
  orders(limit: Int = 10, status: OrderStatus): [Order!]!
}

type Order {
  id: ID!
  total: Float!
  items: [Item!]!
}
RPC-over-HTTP: operation-named URLs with rich JSON payloads·python
# RPC-over-HTTP — POST to an action with rich JSON
# This is what OpenAI's Chat Completions API is, honestly
import httpx

resp = httpx.post(
    'https://api.openai.com/v1/chat/completions',
    headers={'Authorization': 'Bearer sk-...'},
    json={
        'model': 'gpt-4o',
        'messages': [{'role': 'user', 'content': 'Hi'}],
        'temperature': 0.7,
        'stream': True,
    },
)
# /v1/chat/completions is an OPERATION (complete this prompt), not a resource.
# The URL is verb-named; the body is a procedure call. RPC-over-HTTP.
# Honest framing: 'API.'  Dishonest framing: 'REST API.'

External links

Exercise

Pick one feature or service in your work (or invent one). Walk through three design centers: (1) how would you build it as REST, (2) as GraphQL, (3) as gRPC/RPC. For each, list ONE concrete win and ONE concrete cost. Then commit to the choice and articulate in two sentences why this traffic shape fits this style better than the others. Bonus: read a public gRPC .proto file (Envoy, Kubernetes, Etcd all publish theirs) and feel how typed contracts feel different from hand-written OpenAPI.
Hint
REST wins on cacheability + browser-friendliness + intermediary support. GraphQL wins on client flexibility + over-fetch prevention. gRPC wins on typed contracts + HTTP/2 multiplexing + streaming. Costs flip: REST proliferates endpoints if clients vary; GraphQL is harder to cache; gRPC is opaque in browser tools. The articulation exercise — defending the choice in two sentences — is the discipline.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.