The API style debate has gone quieter in the last couple of years — the dust has mostly settled, the use cases have separated, and most teams are no longer trying to use one API style for everything. Still, the question comes up on every new service: REST, GraphQL, or gRPC? This article is the comparison without the religion.
The three styles
HTTP verbs| REST[REST
JSON over HTTP] Q -->|Flexible queries
one endpoint| GraphQL[GraphQL
typed schema] Q -->|RPC calls
typed contracts| gRPC[gRPC
protobuf over HTTP/2] REST --> Servers1[Service] GraphQL --> Servers2[Service] gRPC --> Servers3[Service] style REST fill:#dbeafe,stroke:#1e40af,color:#0c1e3b style GraphQL fill:#fde68a,stroke:#b45309,color:#451a03 style gRPC fill:#e0e7ff,stroke:#3730a3,color:#1e1b4b
Three different mental models. REST organises around resources, GraphQL around queries, gRPC around procedure calls.
REST
The HTTP-as-an-API style. Resources have URLs; HTTP verbs (GET, POST, PUT, DELETE) describe operations on them. Bodies are usually JSON. The contract is implicit — documented in OpenAPI or just in prose.
GET /api/users/42
POST /api/users {body}
PUT /api/users/42 {body}
DELETE /api/users/42
GET /api/users/42/ordersWhat REST does well:
- Universal toolchain. Every language has an HTTP client. Every developer can hit your API with curl or a browser. Documentation tools (Swagger UI, Postman) are mature.
- Cache-friendly. GETs are naturally cacheable, both at the CDN layer and in HTTP intermediaries. Add an ETag header and the client cache becomes excellent.
- Hyperlinkable. Each resource has a stable URL. You can bookmark it, paste it in chat, log it in metrics.
- No build step on the client. A web frontend just makes
fetch()calls. No code generation needed.
What REST does poorly:
- Versioning. Adding a non-breaking field is fine; changing a field's shape requires
/v2/usersand a long deprecation period. Most large REST APIs have at least two versions live at any time. - Over-fetching. A client wanting only a user's name and avatar gets the entire user object including 30 fields it does not need. Bandwidth waste.
- Under-fetching (N+1). A client showing a user with their orders and each order's items needs three round trips: user, orders, items. Latency multiplies.
- No type safety. The server's understanding of a field's type is not shared with the client. Mismatches surface as runtime errors.
GraphQL
One endpoint (typically /graphql), one operation (POST), and a query language that lets the client describe exactly what data it wants.
POST /graphql
{
query: "{
user(id: 42) {
name
avatar
orders(limit: 5) {
id
total
items { name price }
}
}
}"
}One round trip; the response contains exactly the fields requested.
What GraphQL does well:
- Eliminates over-fetching. Client asks for what it needs.
- Eliminates N+1 client-side. The server resolves the whole graph in one request.
- Strongly typed schema. The schema is the contract; tooling (GraphQL Code Generator, Apollo) gives you typed clients.
- Self-documenting. Schema introspection means clients can explore the API interactively.
What GraphQL does poorly:
- Caching. Every query is unique; HTTP-layer caching mostly does not work. Apollo Client and similar tools do client-side normalised caching, which works but is more complex than HTTP cache.
- N+1 server-side. The server's resolvers can issue one database query per object in a list, killing performance unless you use DataLoader to batch.
- Complex authorisation. "Can this user see this field?" needs to be checked field by field, query by query.
- Operational difficulty. Rate limiting, query cost analysis, and abuse prevention are all harder than with REST. A malicious client can issue a deeply nested query that crashes your server.
gRPC
Remote procedure calls over HTTP/2 with Protocol Buffers as the serialisation format. The interface is defined in a .proto file; code generators produce client and server stubs in any language.
// users.proto
service UserService {
rpc GetUser(GetUserRequest) returns (User);
rpc ListOrders(ListOrdersRequest) returns (ListOrdersResponse);
rpc StreamUpdates(StreamRequest) returns (stream UserUpdate);
}
message User {
int64 id = 1;
string name = 2;
string avatar_url = 3;
}What gRPC does well:
- Performance. Binary protobuf payloads are 5–10× smaller than JSON. HTTP/2 multiplexing avoids head-of-line blocking.
- Strongly typed everywhere. Generated client code in any supported language. Compile-time errors instead of runtime ones.
- Streaming. Bidirectional streaming is first-class. Useful for real-time data feeds.
- Service-to-service is the sweet spot. Internal microservices in a data center talking gRPC over fast networks: this is the standard.
What gRPC does poorly:
- Browser support requires a gateway. Browsers cannot speak gRPC natively. You need
grpc-webwith a translation proxy (Envoy is the default). - Less debuggable. No browsing the API in a browser.
grpcurlexists but is less ergonomic than curl. - Toolchain weight. Need to install the protobuf compiler, regenerate code on schema changes, manage
.protofile versions. - Smaller ecosystem. Most public APIs are REST or GraphQL. gRPC dominates internal traffic but rarely external.
Side-by-side
Aspect REST GraphQL gRPC
------------------ -------------- -------------- --------------
Wire format JSON over HTTP JSON over HTTP protobuf/HTTP2
Typing optional strict strict
Versioning URL or header evolve schema evolve proto
Browser support native native requires proxy
Caching excellent limited hard
Learning curve lowest medium highest
Performance medium medium fastest
Streaming SSE / WS hack subscriptions first-class
Best for public APIs mobile + web internal RPCHow to choose
- Public-facing API for third-party developers: REST. Universal toolchain, easy onboarding.
- Mobile or single-page web app with custom data needs: GraphQL. The over-fetching savings on mobile networks are real.
- Internal microservices in a data center: gRPC. Performance and type safety win.
- Real-time / streaming workload: gRPC bidirectional streams or WebSocket-based protocols.
- Webhooks for asynchronous events: REST POST. Universal and simple.
Most companies end up with all three eventually. External-facing REST API for partners; GraphQL for their own mobile app; gRPC between backend services. Trying to use one for everything usually means using the wrong one for some of those.
The new contenders worth mentioning
- tRPC — TypeScript-only RPC. Same code on client and server with full type inference. If your stack is end-to-end TypeScript, tRPC is the simplest answer.
- Connect — Buf's gRPC-compatible protocol that works in browsers. Bridges the gRPC-vs-browser gap.
- JSON-RPC — older RPC pattern, still widely used (Ethereum's API is JSON-RPC). Simpler than REST conventions for procedure-style APIs.
Frequently Asked Questions
Is REST dead?
No. REST APIs power most of the public internet. The complaints about REST's shortcomings are legitimate but apply mainly to high-traffic mobile use cases or internal high-performance scenarios. For most public APIs, REST is still the right answer.
Should I use GraphQL for everything?
No. GraphQL is excellent for clients that fetch lots of related data and want to control exactly which fields. It is overkill for simple CRUD APIs and adds complexity. Use it where its strengths matter; use REST elsewhere.
What is the difference between gRPC and Apache Thrift?
Both are typed RPC frameworks with code generation. gRPC is newer (2015), backed by Google, and has dominated the open-source space. Thrift (originally Facebook) still has installed base in some companies but development has slowed. New projects pick gRPC.
Can I use HTTP/2 with REST?
Yes. HTTP/2 transport is independent of API style. REST over HTTP/2 has multiplexing benefits without changing the API design. Most cloud providers serve REST over HTTP/2 by default.
How do I version an API gracefully?
Three patterns. URL versioning (/v1/, /v2/) is the most explicit. Header versioning (Accept: application/vnd.example.v2+json) is cleaner but less obvious. Schema evolution (add fields, never remove or change) avoids version bumps for most changes. The third is the long-term winning pattern; combine with explicit versions only for genuinely breaking changes.
Share your thoughts
Worked with this in production and have a story to share, or disagree with a tradeoff? Email us at support@mybytenest.com — we read everything.