REST vs gRPC vs GraphQL in 2026
There is no single winner. REST, gRPC, and GraphQL solve different problems at different boundaries, and the teams that pick one for everything usually regret it within a year. I've run REST for public charging APIs, gRPC between the OCPP middleware and session service, and considered GraphQL for a mobile app that needed wildly different screen payloads from the same backend. Each choice made sense at its boundary. The mistake is treating API style as a company-wide religion instead of a per-interface decision.
The quick comparison
| Dimension | REST | gRPC | GraphQL |
|---|---|---|---|
| Transport | HTTP/1.1 or HTTP/2 | HTTP/2 required | HTTP (usually POST) |
| Payload format | JSON (text) | Protobuf (binary) | JSON (text) |
| Contract | OpenAPI/Swagger | .proto files |
GraphQL schema |
| Browser support | Native | Needs grpc-web proxy | Native |
| Streaming | SSE, WebSockets (add-on) | Bidirectional streams (native) | Subscriptions (add-on) |
| Caching | HTTP caching (built-in) | None at HTTP level | Client-side normalized cache |
| Best for | Public APIs, CRUD, webhooks | Service-to-service, streaming | Mobile/web apps, varied views |
| Tooling maturity | Universal | Strong in polyglot backends | Strong in frontend ecosystems |
REST: still the default for a reason
REST over HTTP with JSON is the lingua franca. Every client library, every API gateway, every monitoring tool understands it. If third parties integrate with your API — payment webhooks, partner integrations, public developer docs — REST is the path of least resistance.
Where REST earns its keep:
- Public-facing APIs with OAuth, rate limiting, and versioning (
/v1/,/v2/). - CRUD-heavy resources where the data shape is stable and clients fetch whole objects.
- Webhook callbacks —
POST /webhooks/stripeis REST whether you like it or not. - Caching —
ETag,Cache-Control, CDN-friendly GET endpoints.
Where REST strains:
- Over-fetching — mobile app needs
{ id, status, chargerName }but the endpoint returns 40 fields. - Chatty clients — five REST calls to render one screen (the N+1 problem at the API level).
- Real-time — REST is request/response; pushing state requires bolting on WebSockets or SSE.
GET /api/v1/sessions/active HTTP/1.1
Authorization: Bearer eyJ...
{
"sessions": [{
"id": "sess_42",
"status": "charging",
"charger": { "id": "cp_7", "name": "Bay 3", "location": "..." },
"meterValues": [ ... ] // client only needed status
}]
}
gRPC: the internal highway
gRPC uses Protocol Buffers for compact binary serialization and HTTP/2 for multiplexed connections. In benchmarks, protobuf payloads are 3–10x smaller than equivalent JSON, and deserialization is faster. For service-to-service calls at scale — session service talking to billing service, middleware fanning out to ten backends — that difference matters.
service SessionService {
rpc StartSession(StartSessionRequest) returns (Session);
rpc StreamMeterValues(StreamRequest) returns (stream MeterValue);
}
message StartSessionRequest {
string charger_id = 1;
string user_id = 2;
string idempotency_key = 3;
}
Strengths:
- Strongly typed contracts — the
.protofile is the API. Code generation produces client/server stubs in any language. - Streaming — bidirectional streams for meter value ingestion, log tailing, or any long-lived data flow.
- Performance — lower latency and CPU than JSON REST for high-throughput internal calls.
Weaknesses:
- No browser support — you need grpc-web and an Envoy proxy, which adds ops complexity.
- Poor human debuggability — you can't
curla protobuf endpoint and read the response. - Load balancer compatibility — not all L7 load balancers handle HTTP/2 trailing headers correctly.
My rule: gRPC for service-to-service, never for the mobile client. The charging platform's middleware spoke gRPC to the session and billing services internally, while the Flutter app got a REST/WebSocket facade through the BFF layer.
GraphQL: client-driven queries
GraphQL lets the client specify exactly what fields it needs in a single request. One query, one round trip, no over-fetching:
query ActiveSession {
activeSession {
id
status
charger { name }
}
}
Strengths:
- Eliminates over-fetching and under-fetching — each screen requests exactly its shape.
- Single endpoint — no versioning debates (
/v1vs/v2); deprecate fields in the schema. - Strong frontend tooling — Apollo, Relay, normalized client caches.
Weaknesses:
- Complexity at the server — resolvers, N+1 query problems, DataLoader patterns, query cost analysis.
- Caching is hard — HTTP caching doesn't apply to POST-based GraphQL queries.
- Abuse surface — a deeply nested query can DDOS your database without query cost limits.
GraphQL makes sense when you have one backend serving multiple clients with very different data needs — a mobile app, a web dashboard, and a partner portal all reading from the same domain. It does not make sense for a simple CRUD API with two endpoints.
The 2026 decision framework
Ask these questions in order:
- Who is the client? Third parties → REST. Your own services → gRPC. Your own apps with varied views → GraphQL.
- Does the client need real-time push? None of these are great at it. Add WebSockets or SSE alongside whatever you pick.
- What's the team's expertise? A GraphQL server with poorly written resolvers is worse than a boring REST API.
- What's the traffic pattern? High-throughput internal fan-out → gRPC. Occasional mobile fetches → REST or GraphQL.
| Boundary | My default in 2026 |
|---|---|
| Public/partner API | REST + OpenAPI |
| Service-to-service | gRPC |
| Mobile app data layer | GraphQL BFF (or REST BFF if team is small) |
| Real-time state | WebSocket (alongside any of the above) |
| Admin/internal tools | REST (speed of development wins) |
Mixing styles without chaos
Most production systems use two or three styles. The rules that keep it sane:
- One style per boundary. The mobile app talks to the BFF; the BFF talks gRPC to services. Don't expose gRPC directly to the app.
- Generate, don't hand-write. OpenAPI codegen for REST clients, protobuf codegen for gRPC, GraphQL codegen for frontend types. Hand-written clients drift.
- Observability spans all styles. Trace IDs propagate whether the hop is HTTP, gRPC, or GraphQL. Same dashboards, same alerts.
The API style is an implementation detail at each boundary, not an identity. Pick what fits the client, the team, and the traffic — and revisit when any of those change.
Resources
- gRPC official documentation
- GraphQL specification
- Martin Fowler — Richardson Maturity Model
- Protocol Buffers documentation
- Apollo GraphQL — Best practices
- Google Cloud API Design Guide
Frequently asked questions
When should I choose gRPC over REST?
When you control both client and server, need low-latency binary serialization, or have high-throughput service-to-service calls. gRPC shines internally; it's awkward in browsers without a proxy.
Is GraphQL replacing REST?
No. GraphQL solves client-driven data fetching for apps with diverse views over the same backend. REST remains simpler for public APIs, webhooks, and CRUD-heavy services where clients don't need query flexibility.
Can I use multiple API styles in one system?
Yes, and most mature systems do. gRPC between services, REST for public/third-party APIs, GraphQL for the mobile app BFF. Pick per boundary, not per company.
Hiring a senior Android / Flutter engineer?
I architect and ship production mobile software — Kotlin, Jetpack Compose, Flutter — for robotics, EV infrastructure, fintech, and real-time systems. Open to remote roles in Europe and the US.
Get in touch →