The first time The remaining step was to pick between REST, GraphQL, and gRPC, It pickeds wrong. We were building a logistics platform with a complex domain — shipments, carriers, routes, customs documents, weather — and It wents with REST because "that's what we know." Eighteen months later we had 200+ endpoints, every frontend team was over-fetching or under-fetching, and we were rewriting half the API surface to support a new mobile app. GraphQL would have been a better fit from day one. But It would never built one in production, so It avoideds it. The lesson: pick the tool that matches the problem, not the tool you already know.
Why This Matters
In 2026, "which API style should Used?" is one of the most consequential technical decisions a team makes. It's also one of the most politicized. Engineers tend to have strong opinions about REST, GraphQL, and gRPC — opinions often rooted in identity rather than evidence. "We're a GraphQL shop" is sometimes a fact about the codebase and sometimes a flag the team flies. The result is that real engineering decisions get made on vibes, not analysis.
The cost of picking wrong is not abstract. A team It consulteds with in 2024 had chosen gRPC for a public partner API. They had the engineering chops to do it right, but their partners didn't. Six months in, only two of eight partners had working integrations, and the team was rebuilding the same surface as REST anyway. They'd spent roughly $400,000 in engineering time building a system that worked great internally and was unusable externally.
The other thing that's changed in 2026 is the rise of AI-generated clients. AI tools generate code against whatever spec you give them. If you give them an OpenAPI document, they generate REST clients. If you give them a GraphQL schema, they generate GraphQL clients. If you give them a proto file, they generate gRPC stubs. The choice of API style affects not just your developer experience but the experience of every AI tool your team uses. Pick the one that lets your tools — and your team — be most productive.
The Core Idea
Let's define each style crisply.
REST is an architectural style that uses HTTP as the transport and JSON (or XML) as the payload. Resources are identified by URLs, actions are represented by HTTP methods, and the contract is implicit — defined by your code, your docs, and your behavior. REST is the lingua franca of the web. Every engineer knows it. Every HTTP client library supports it. Every CDN, gateway, proxy, and observability tool understands it. The downside is that the URL structure is a fixed contract: every endpoint returns a fixed shape, and clients either get too much data or too little.
GraphQL is a query language for your API. There's typically a single endpoint (e.g., /graphql) that accepts a query describing exactly what the client wants. The server resolves the query, fetching from any number of underlying sources, and returns a JSON object matching the requested shape. Clients get exactly the fields they need, no more and no less. The downside is operational complexity: you need a schema, you need resolvers, you need to think about N+1 queries, you need to handle authorization at the field level, and you need tooling to make all that manageable.
gRPC is a high-performance RPC framework that uses HTTP/2 as the transport and Protocol Buffers (or alternatives like FlatBuffers) as the payload format. You define your service in a .proto file, generate client and server stubs in your language of choice, and call methods as if they were local functions. gRPC is fast (binary serialization, multiplexed streams), strongly typed (the proto is the contract), and great for service-to-service communication. The downside is that it's not human-readable (protobufs are binary), it's hard to debug from a curl command, and it's a poor fit for browser clients without a proxy layer.
Here's the high-level tradeoff matrix:
| Concern | REST | GraphQL | gRPC |
|---|---|---|---|
| Browser support | Excellent | Excellent | Needs proxy |
| Mobile support | Excellent | Excellent | Good |
| Service-to-service | OK | OK | Excellent |
| Tooling for humans | Excellent | Excellent | Limited |
| Tooling for machines | Excellent | Excellent | Excellent |
| Performance | OK | OK | Excellent |
| Schema enforcement | Optional (OpenAPI) | Built-in | Built-in (proto) |
| Streaming | Limited (SSE) | Subscriptions | Bidirectional |
| Caching | Built-in (HTTP) | Complex | Custom |
| Versioning | Easy | Hard | Easy |
| Public API fit | Excellent | Good | Poor |
The way The evidence suggests about it in 2026: REST is the default, GraphQL is the right choice when clients have very different data needs, and gRPC is the right choice when you control both ends and performance matters.
A Concrete Example
Let's build the same endpoint in three styles — "get a user with their last 5 orders" — and compare.
REST
// GET /v1/users/123/orders?limit=5
const response = await fetch('https://api.example.com/v1/users/123/orders?limit=5', {
headers: { Authorization: `Bearer ${token}` },
});
const { data: orders } = await response.json();
// Orders include minimal user info; if we need more, we'd have to make another call
Two round trips if you also need the user record (GET /v1/users/123, then GET /v1/users/123/orders). Response shape is fixed by the server.
GraphQL
query GetUserWithOrders {
user(id: "123") {
id
fullName
email
orders(last: 5) {
id
total
status
createdAt
}
}
}
const response = await fetch('https://api.example.com/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({ query: GET_USER_WITH_ORDERS }),
});
const { data } = await response.json();
One round trip, exactly the fields requested. The server uses a single resolver (or a series of resolvers) to fulfill the query.
gRPC
// user_service.proto
syntax = "proto3";
package ledge.v1;
service UserService {
rpc GetUser(GetUserRequest) returns (User);
rpc GetUserWithOrders(GetUserRequest) returns (UserWithOrders);
}
message GetUserRequest {
string id = 1;
}
message User {
string id = 1;
string full_name = 2;
string email = 3;
}
message Order {
string id = 1;
int64 total_cents = 2;
string status = 3;
string created_at = 4;
}
message UserWithOrders {
User user = 1;
repeated Order recent_orders = 2;
}
// Generated client code
import { UserServiceClient } from './generated/user_service';
const client = new UserServiceClient('api.example.com:443', credentials);
const response = await client.getUserWithOrders({ id: '123' });
console.log(response.user, response.recentOrders);
Strongly typed, binary-serialized, fast. But you can't curl this — you need the generated stub. And the contract is the proto file, not something a human reads naturally.
Side-by-side comparison
Let's look at what changes when you ship the same feature in each style.
Adding a new field "phone number" to User:
- REST: Add the field to your serializer. Document it. It's automatically returned. No version bump needed (assuming JSON clients tolerate unknown fields, which most do).
- GraphQL: Add the field to the schema. Existing clients don't see it until they update their queries. No breaking change, but tooling needs to know.
- gRPC: Add the field to the proto. Regenerate stubs. Old clients ignore the field at the binary level (proto3 semantics). No breaking change unless you change a field number.
Removing the "fax number" field from User:
- REST: Breaking change. Bump version. Provide v2.
- GraphQL: Mark the field as
@deprecated(reason: "Use phoneNumber instead"). After a deprecation period, remove it. Breaking change at removal time. - gRPC: Technically possible to remove a field, but you lose the field number forever. Practically, mark it deprecated and stop populating it.
Adding a new endpoint:
- REST: Trivial. Add the route.
- GraphQL: Add a new query or mutation to the schema.
- gRPC: Add a new RPC method to the service definition.
Streaming server-sent events:
- REST: SSE works but requires custom framing and isn't standardized across frameworks.
- GraphQL: Built-in
Subscriptiontype, supported by major libraries (Apollo, Relay, urql). - gRPC: Native bidirectional streaming. The most efficient of the three.
Mobile performance on slow networks:
- REST: Multiple round trips. Each is a full HTTP request.
- GraphQL: One round trip per query, but queries can be arbitrarily complex.
- gRPC: HTTP/2 multiplexing + binary = fastest, especially for many small requests.
Cacheability:
- REST: HTTP semantics give you caching for free. CDN-friendly.
- GraphQL: POST requests with bodies aren't cached by default. Need persisted queries, custom caching headers, or Apollo's cache. Hard.
- gRPC: No HTTP semantics. Need a custom caching layer (Redis, memcached).
These aren't academic differences. They shape how your system behaves under load.
Common Pitfalls
1. Treating REST as "no design." REST without conventions is just chaos. You still need a design guide — URL patterns, error format, pagination, versioning. Picking REST doesn't mean you can skip the work.
2. Treating GraphQL as a magic performance fix. GraphQL can solve over-fetching, but it can also create N+1 query storms if your resolvers aren't careful. A naïve GraphQL layer over a relational database can be slower than the REST equivalent. Use DataLoader or similar batch-loading patterns.
3. Exposing gRPC to public clients. gRPC works beautifully between services you control. It works terribly when partners have to integrate. Unless your partners are willing to generate stubs from your proto files (which most aren't), pick REST or GraphQL for public APIs.
4. Mixing styles without a strategy. Some teams ship REST for the public API and GraphQL for the internal dashboard and gRPC for service-to-service. That's fine if it's deliberate. If different teams picked different styles without coordination, you'll have a mess.
5. Forgetting authorization in GraphQL. REST endpoints have authorization at the URL level. GraphQL has authorization at the field level. That means you need to think about who can see user.email vs user.internalScore separately. Easy to get wrong.
6. Not budgeting for the GraphQL ecosystem. Apollo, Relay, urql, GraphQL Code Generator, GraphQL Inspector, the testing tools — they all exist, and they're all necessary to run GraphQL in production. The library stack is heavier than REST or gRPC.
7. Ignoring streaming requirements. If you need server-push, real-time updates, or bidirectional communication, plan for it from day one. Retrofitting streaming onto REST is painful; adding it to GraphQL is moderate; gRPC has it built in.
When to Use This (And When Not To)
Use REST when:
- You're building a public or partner-facing API
- You want browser-native caching and tooling
- Your clients have similar data needs (most dashboards, most admin panels)
- Your team is small and you want to ship fast
- You're unsure — REST is the safest default
Use GraphQL when:
- You have multiple clients with very different data needs (web, mobile, partner integrations)
- Your domain is graph-shaped (social networks, content sites, CRMs)
- You're constantly shipping "add a new field" changes and don't want to coordinate with every frontend team
- You can invest in the operational tooling (Apollo or similar, persisted queries, observability)
Use gRPC when:
- You're building service-to-service communication in a microservices architecture
- Performance and low latency are critical (high-frequency trading, real-time games, video processing)
- You control both ends and can share proto files
- You need bidirectional streaming
Common hybrid patterns in 2026:
- REST + gRPC: REST for the public API, gRPC for internal service-to-service. This is what Netflix, Google, and most large tech companies do.
- GraphQL as a layer over REST or gRPC: build the canonical resource layer in REST/gRPC, expose a thin GraphQL facade for clients that need flexibility. This is what Shopify, GitHub, and others have done.
- REST + GraphQL gateway: REST is the source of truth, GraphQL is generated on top.
Wrapping Up
The decision between REST, GraphQL, and gRPC isn't a religion. It's a tradeoff matrix. For most teams in 2026, the answer is REST for public APIs, gRPC for internal services, and GraphQL where you have proven heterogeneity in client needs. Many of the best systems use two or even all three.
Your next step: look at your current API. Is it the right shape for the clients that consume it? If you're seeing over-fetching or under-fetching complaints, GraphQL might earn its complexity. If you're seeing latency complaints from internal services, gRPC might be worth the proto-file overhead. Otherwise, REST is probably still the right answer — and that's fine.
Further Reading
- GraphQL spec — the authoritative reference
- gRPC documentation — concepts, tutorials, and best practices
- Apollo GraphQL — the most-used GraphQL server and client
- Stripe's gRPC services — a real-world gRPC deployment
- How Netflix uses GraphQL — a detailed case study
- REST API Design Rulebook — Mark Masse — a pragmatic REST reference
Hermes Smith
