REST vs GraphQL vs gRPC: When to Use What in 2026
Three years ago I spent two weeks rewriting the internal API of our logistics platform. We’d started with REST, got convinced by a…
REST vs GraphQL vs gRPC: When to Use What in 2026
Three years ago I spent two weeks rewriting the internal API of our logistics platform. We’d started with REST, got convinced by a well-meaning consultant to migrate to GraphQL, then ended up with a hybrid mess I’m still partially embarrassed about. The good news: that mess taught me more about API design than any conference talk ever has.

The “which API style should I use?” question surfaces on every project I start and every team I talk to. Most answers online are either too theoretical — here’s what REST is, here’s what GraphQL does — or too tribal (gRPC is the future, REST is dead, etc.). Neither helps when you’re in a planning meeting on a Monday morning trying to make a concrete decision.
Here’s what I’ve found after running REST endpoints in production for years, shipping a GraphQL API for a Flutter mobile app, and using gRPC for internal service communication in a Spring Boot microservices setup. No theoretical purity. Just the patterns that worked and the ones that didn’t.
The answer, predictably, is “it depends” — but the specific things it depends on are actually well-defined once you’ve been burned enough times.
REST: Still the Default, for Good Reasons
REST is boring in the best way. Almost every developer knows it, almost every tool supports it, and when something breaks you can debug it with curl and a browser's network tab. I still reach for REST first on most projects.
The mental model is simple: resources have URLs, HTTP verbs describe what you want to do to them. GET /users/42, POST /orders, DELETE /sessions/current. Teams that have never worked together can usually agree on a REST contract without much friction.
Where REST shines is external-facing APIs, public integrations, and anywhere you don’t control the client. If you’re building an API that third parties will consume, REST with OpenAPI documentation is still the pragmatic choice. OAuth2, rate limiting, API gateways — all the surrounding infrastructure is optimized for it.
@RestController
@RequestMapping("/api/v1/orders")
public class OrderController {
@GetMapping("/{id}")
public ResponseEntity<OrderDto> getOrder(@PathVariable Long id) {
return orderService.findById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@PostMapping
public ResponseEntity<OrderDto> createOrder(@RequestBody @Valid CreateOrderRequest req) {
OrderDto created = orderService.create(req);
URI location = URI.create("/api/v1/orders/" + created.getId());
return ResponseEntity.created(location).body(created);
}
}
The friction starts when clients need data that doesn’t map cleanly to your resource hierarchy. A mobile screen might need the user, their last five orders, and their notification count — three round trips, or one overfetched /dashboard endpoint you added because the mobile team was complaining about latency. That's the moment people start asking about GraphQL.
GraphQL: Powerful, but the Tradeoffs Are Real
I’ve built GraphQL APIs twice. The first time, for a mobile Flutter app, it solved a genuine problem. The second time, for an internal admin panel, it was overkill and I should have known better.
GraphQL is genuinely good at one specific thing: letting clients ask for exactly the data they need. When you have multiple client types — mobile, web, third-party — with different data requirements hitting the same backend, GraphQL removes the negotiation that otherwise happens between frontend and backend teams. The schema becomes a contract, the type system catches mismatches at development time, and you stop adding one-off REST endpoints to satisfy specific screen requirements.
query GetOrderDashboard($userId: ID!) {
user(id: $userId) {
name
email
recentOrders(limit: 5) {
id
status
total
createdAt
}
unreadNotificationCount
}
}
One request. The client specifies exactly what it needs. The backend resolves each field. Clean.
The real cost is on the backend. N+1 query problems appear immediately if you’re not careful — you need DataLoader or a similar batching mechanism from day one. Caching is harder because you can’t cache at the HTTP layer the same way you do with REST (every query goes to POST /graphql). You need a proper schema registry once more than two or three developers are touching it. Monitoring becomes more complex because POST /graphql tells you nothing — you need to track operation names.
There’s also the security surface. REST endpoints make it easy to reason about what’s exposed and to apply rate limiting or authorization at the route level. With GraphQL, you need to think carefully about query depth limits, query complexity analysis, and field-level authorization. I’ve seen teams ship GraphQL APIs without any of this and regret it within months.
My honest take: use GraphQL when you have a genuinely complex data graph, multiple client types with divergent needs, and the team has the experience to operate it properly. Don’t use it because it sounds modern or because a startup you admire uses it.
gRPC: The Protocol Built for Service-to-Service
gRPC comes from a different set of problems entirely. Where REST and GraphQL are primarily about exposing data to clients, gRPC is designed for communication between services — particularly in a microservices architecture where performance, type safety, and contract enforcement matter.
The foundation is Protocol Buffers: a binary serialization format and schema language. You define your service contracts in .proto files, run the code generator, and get type-safe clients and server stubs in your language of choice. Change a field name and your build breaks at compile time. That alone is worth a lot in a codebase with multiple teams.
syntax = "proto3";
package inventory;
service InventoryService {
rpc GetStock (StockRequest) returns (StockResponse);
rpc WatchStock (StockRequest) returns (stream StockUpdate);
rpc BatchUpdateStock (stream StockUpdateRequest) returns (BatchUpdateResponse);
}
message StockRequest {
string product_id = 1;
string warehouse_id = 2;
}
message StockResponse {
string product_id = 1;
int32 quantity = 2;
string last_updated = 3;
}
The performance argument is real. Binary encoding is more compact than JSON. HTTP/2 multiplexing reduces latency. For high-throughput internal traffic — inventory checks, payment validations, event fan-out — the difference shows up in production metrics.
The streaming support is genuinely useful in ways REST can’t easily replicate. Bidirectional streaming lets you build real-time features (live stock updates, order tracking) without WebSockets bolted onto a REST architecture.
What gRPC is bad at: anything involving a browser (gRPC-Web exists but it’s still friction), public APIs (Proto files are not developer-friendly onboarding material), and teams without experience with the Protobuf toolchain. The generated code can be magical in a frustrating way when something goes wrong.
I use gRPC exclusively for internal service-to-service calls in our Spring Boot microservices setup. The contract enforcement alone — knowing that if OrderService compiles against the generated stubs it's talking to a compatible version of InventoryService — has caught breaking changes before they hit production more times than I can count.
How I Actually Decide

The questions I ask, in rough order:
Who is the caller? External third parties or browsers? REST. If you need to support browsers with streaming, REST with Server-Sent Events. Internal services in a language with good gRPC support? gRPC.
How many client types with different data needs? If you have mobile, web, and B2B integrations all hitting the same backend and the data requirements diverge significantly, GraphQL starts to earn its complexity cost. One client type, or stable data requirements? REST is simpler.
How performance-sensitive is it? For most CRUD operations, REST over JSON is fine. For high-frequency internal calls — thousands per second, hard latency budgets — gRPC’s binary encoding and HTTP/2 multiplexing make a measurable difference.
Does your team know the technology? This sounds obvious but I’ve watched teams choose GraphQL because it’s interesting, ship it half-baked, and spend months dealing with N+1 problems and missing authorization checks. A boring technology your team knows cold beats an interesting one they don’t.
What We Actually Run in 2026
External APIs serving mobile clients and third-party integrations: REST with OpenAPI specs. The contract is stable, the tooling is standard, new integrators are productive in an afternoon.
Our Flutter app uses GraphQL for the main data-fetching layer, specifically because we have a web dashboard and a mobile app with different screen layouts hitting the same backend. The schema has become the shared language between frontend and backend teams. When we add a feature, the schema PR goes out first.
Internal microservice communication is almost entirely gRPC. The Spring Boot services expose gRPC endpoints for synchronous calls. Async event flows go through Kafka, not gRPC — streaming in gRPC is powerful but long-lived connections between services create coupling I’d rather handle with a message broker.
Is there a hybrid cost? Yes. Three different mental models, three different debugging approaches, three different sets of tooling. I don’t think it’s avoidable given the genuinely different requirements. What I try to avoid is mixing approaches within the same layer — the external API is REST, not “REST except for the complex screens where we also added a GraphQL endpoint.”
The teams that get into trouble are the ones that pick a single company-wide protocol and then fight the constraints when a use case doesn’t fit. The constraints are real. REST overfetching is a real problem at scale. GraphQL complexity is a real operational cost. gRPC developer experience outside internal systems is genuinely painful. Knowing when each applies is more useful than picking a winner.
What’s your current setup — one protocol everywhere, or a mix? And if you’ve run GraphQL in production for more than a year, I’m curious whether the N+1 problems were something you solved cleanly or something you’re still working around.
메타데이터
- post_id
- ea3392d41476
- slug
- rest-vs-graphql-vs-grpc-when-to-use-what-in-2026-ea3392d41476
- url
- https://medium.com/@davide.mib/rest-vs-graphql-vs-grpc-when-to-use-what-in-2026-ea3392d41476
- canonical_url
- https://medium.com/@davide.mib/rest-vs-graphql-vs-grpc-when-to-use-what-in-2026-ea3392d41476
- author_url
- https://medium.com/@davide.mib
- status
- ok
- fetched_at
- 2026-06-09 15:37:30