REST, gRPC, GraphQL: When to Use Each (And Why Teams Pick Wrong)
The default answer to “what API protocol should we use?” has historically been REST. It remains the default. Most of the time, that’s fine. But “most of the time” isn’t all the time, and teams that keep picking REST out of habit pay taxes later.
Here’s a framework for actually choosing.
REST: Default for Understandable Reasons
What it is: HTTP verbs (GET, POST, PUT, DELETE) on predictable resource paths. /users/123, /posts/456/comments. Stateless. Cacheable. Firewall-friendly.
Why it dominates: HTTP is ubiquitous. Every load balancer, CDN, and proxy understands it. Caching lives for free at the edge. Your browser speaks it natively.
The cost:
- Every different data shape needs a different endpoint (or you over-fetch, polluting the network).
- Versioning is painful (is it
/v1/usersor?version=1?). - Batch operations are awkward (
POST /users/bulk?). - Real-time is bolted on (WebSockets, long-polling).
Pick REST when:
- Public APIs where clients are decoupled from you (GitHub, Stripe, Twitter). You need HTTP caching, proxies, and observability tools that speak HTTP.
- Simple CRUD with predictable access patterns. You’re not doing complex filters, joins, or nested resources.
- Browsers and mobile clients where you want implicit caching and CDN benefits.
- You don’t control both client and server (third-party integrations).
Example: A public payment gateway API. Endpoint stability, caching behavior, and being firewall-friendly matter more than latency.
gRPC: Default When Speed and Volume Matter
What it is: Binary protocol (Protocol Buffers) over HTTP/2. Multiplexing. Streaming built-in. Statically typed schemas.
Why it wins for speed:
- Binary encoding is ~10x smaller than JSON over the wire.
- HTTP/2 multiplexing means one connection handles 100s of concurrent requests.
- Protobuf code generation means no serialization surprises.
- Server-to-server communication doesn’t need the flexibility of REST.
The cost:
- Not browser-friendly (no gRPC from JavaScript natively until grpc-web).
- Not CDN-cacheable (it’s binary, and caching microbiology depends on your gateway).
- Requires Protobuf literacy on the client side.
- Firewalls sometimes block it (though it’s just HTTP/2).
Pick gRPC when:
- High-volume service-to-service communication. You control both client and server. Latency and throughput are metrics that matter.
- Streaming is central to your workload. gRPC streaming is built-in; REST streams are a hack with chunked HTTP responses.
- Low latency is a constraint. Every millisecond of serialization overhead matters.
- Internal APIs. You don’t need to support browser clients or third-party integrations.
Example: Payment processing pipeline where Order Service → Ledger Service → Analytics Service churn through millions of events per second. gRPC’s efficiency pays for the operational complexity.
GraphQL: Default When Clients Are Diverse and Complex
What it is: Strongly typed query language. Clients ask for exactly what they need. Server returns exactly that, nothing more, nothing less.
Why it wins for client flexibility:
- No more
/users?fields=id,name,email,posts[0:10]hack endpoints. You ask, you get. - One endpoint. No versioning headaches (you just add new fields; old clients keep working).
- Nested queries in one request (no N+1 problem on the client’s network).
- Real-time subscriptions are first-class (not WebSockets bolted on).
The cost:
- Requires a GraphQL server (not trivial to build correctly).
- Query complexity can explode (a nested query that traverses 5 levels deep might hammer your database).
- Caching is complex (HTTP caching doesn’t work for POST queries; you need Apollo Client or similar).
- Debugging is harder (one query hits multiple resolvers; tracing matters).
- Every query becomes expensive if you’re not careful (cost analysis tooling is young).
Pick GraphQL when:
- Multiple clients with different data needs. Web, mobile, TV dashboard—each wants different shapes of the same data.
- Query complexity is client-side. The backend shouldn’t need to know every possible combination of fields.
- Rapid iteration on the client. Designers want new fields; engineers don’t want to negotiate with the backend API team.
- Internal APIs where cost analysis can be a design constraint (e.g., “no query can hit more than 3 databases”).
Example: A content platform with web (desktop browsing, heavy pagination), mobile (minimal bandwidth, just summaries), and TV (rich media assets). Each client queries exactly what it needs. No more /users/profile-web-optimized endpoints.
Decision Matrix
| Dimension | REST | gRPC | GraphQL |
|---|---|---|---|
| Latency | Medium (JSON overhead) | Low (binary, multiplexing) | Medium (depends on implementation) |
| Bandwidth | High (JSON, overfetch) | Low (binary, exact fields) | Low (query projection) |
| Ease of Use | High (HTTP, URLs, curl) | Low (Protobuf, binary debugging) | Medium (query language learning curve) |
| Caching | Excellent (HTTP edge caching) | Poor (binary, stateless, no proxies) | Hard (POST queries, need client-side cache) |
| Real-time | Bolted on (WebSockets) | Built-in (streaming) | Built-in (subscriptions) |
| Complexity Management | Multiple versioned endpoints | Single schema, protobuf versioning | Single schema, backwards compatible |
| Browser-friendly | Yes | No (needs grpc-web) | Yes |
| Multi-tenant isolation | Simple (per-endpoint auth) | Simple (per-stream context) | Hard (query cost analysis) |
The Hybrid Approach (Most Real Systems)
Most large systems use all three:
-
REST for public APIs: Twilio, Stripe, AWS—everything external speaks REST because caching and HTTP ubiquity matter.
-
gRPC for internal microservices: Payment service → order service → analytics. Speed and volume are constraints. Protobuf schemas are contracts.
-
GraphQL for client-facing data aggregation: The web/mobile client talks to a GraphQL gateway that composes data from internal gRPC services. Clients get flexibility; the backend stays fast.
This is expensive to build (you’re maintaining 3 API surface layers). But it’s the cost of scale—each protocol solves a different constraint.
What Most Teams Get Wrong
-
“We’ll start with REST and migrate to gRPC later.” No. The cost of migrating is high (schema changes, client updates, coordination). Pick early based on constraints, not convenience.
-
“GraphQL will solve all our API problems.” It solves client flexibility. It creates new problems (query cost analysis, N+1 queries, caching). It’s not a pure win.
-
“gRPC is faster, so we should use it for everything.” Speed matters if the network is the bottleneck. If your bottleneck is database latency, both are equally fine. You’re optimizing the wrong thing.
-
“Public APIs should be GraphQL.” Public APIs should be REST (because caching and simplicity and HTTP ubiquity matter). GraphQL shines for internal/controlled clients.
The Honest Take
REST is safe. It’s the default for a reason. Use it unless you have a specific constraint (latency, bandwidth, client complexity) that it doesn’t solve.
gRPC wins when you control both ends and latency matters. If you’re building a distributed ledger backend or a payment processor, the efficiency gains pay for the complexity.
GraphQL wins when clients are diverse and control matters. If you’re building a platform with 5 different client types and slow iteration cycles, the query flexibility pays for the operational burden.
Most teams default to REST and regret it later (they wish they’d picked gRPC for internal services). Some teams pick GraphQL first and regret the caching complexity.
The smarter choice: pick based on constraints, not trends.
The honest reality: you’ll probably build all three before you’re done.