REST vs GraphQL vs gRPC is not a contest with one winner. REST is the safest default for public, resource-oriented APIs that benefit from HTTP caching. GraphQL shines when many different clients need flexible views of connected data. gRPC is the strongest choice for fast, strongly typed communication between internal services.
The rest of this article explains how each style works, where each one hurts, and a simple way to pick one for a new API without regretting it a year later.
What is REST?
REST models your system as resources identified by URLs and manipulated with standard HTTP methods. A user is /users/42, their orders are /users/42/orders, and you read, create, update, or delete them with GET, POST, PUT/PATCH, and DELETE. Responses are usually JSON.
The biggest strength of REST is that it leans on HTTP itself. Status codes, caching headers, content negotiation, and authentication schemes all work the way browsers, proxies, and CDNs already expect. A GET response with Cache-Control can be cached at every layer between client and server with no extra work.
The typical weaknesses are over-fetching (the endpoint returns more fields than the screen needs) and under-fetching (a screen needs three round trips to assemble its data). Both are manageable with careful API design, but they grow as the number of client types grows. See the REST guide for deeper coverage.
What is GraphQL?
GraphQL exposes a single endpoint and a typed schema. The client sends a query describing exactly the fields it wants, and the server returns data in the same shape.
query OrderScreen($id: ID!) {
user(id: $id) {
name
orders(last: 5) {
id
total
items { productName quantity }
}
}
}
One request, exactly the fields the screen needs, across three related types. That is the core appeal. The schema also acts as a contract and powers excellent tooling: autocomplete, type generation for clients, and schema diffing in CI.
The costs appear on the server side. Every field is backed by a resolver, and naive resolvers produce the N+1 query problem, where fetching 50 orders triggers 50 separate item lookups. Batching tools such as DataLoader fix this, but you have to remember to use them. Because most queries go through POST to one URL, standard HTTP caching does not help much, so caching moves into the client or into persisted queries. You also need guardrails like query depth limits and cost analysis so one client cannot ask for the whole graph. The GraphQL guide covers these patterns.
What is gRPC?
gRPC is a remote procedure call framework. You define services and messages in Protocol Buffers, generate client and server code in your language of choice, and call remote methods as if they were local functions. It runs over HTTP/2 and serializes messages in a compact binary format.
syntax = "proto3";
package orders.v1;
service OrderService {
rpc GetOrder(GetOrderRequest) returns (Order);
rpc StreamOrderUpdates(GetOrderRequest) returns (stream OrderUpdate);
}
message GetOrderRequest {
string order_id = 1;
}
message Order {
string order_id = 1;
int64 total_cents = 2;
repeated string item_ids = 3;
}
message OrderUpdate {
string order_id = 1;
string status = 2;
}
Binary encoding and HTTP/2 multiplexing make gRPC efficient on the wire, and generated stubs give you compile-time type safety across service boundaries. It also supports server, client, and bidirectional streaming natively.
The trade-off is reach. Browsers cannot speak native gRPC directly, so web clients need gRPC-Web and a proxy. Payloads are not human-readable, which makes ad-hoc debugging with curl harder, and some load balancers and proxies need specific configuration for long-lived HTTP/2 connections. The gRPC guide goes deeper.
REST vs GraphQL vs gRPC: side-by-side comparison
| Dimension | REST | GraphQL | gRPC |
|---|---|---|---|
| Data model | Resources and URLs | Typed graph schema | Services and methods |
| Transport | HTTP/1.1 or HTTP/2 | Usually HTTP POST | HTTP/2 |
| Payload format | JSON (typically) | JSON | Protocol Buffers (binary) |
| Contract | Optional (OpenAPI) | Required (schema) | Required (.proto) |
| HTTP caching | Excellent | Limited | Limited |
| Browser support | Native | Native | Needs gRPC-Web proxy |
| Streaming | Via SSE or WebSockets | Subscriptions | Built in, bidirectional |
| Best fit | Public APIs, CRUD | Varied frontends, aggregation | Internal service calls |
How does performance compare?
Performance differences are real but often smaller than people expect, because most latency lives in databases and downstream calls rather than serialization.
- gRPC usually has the smallest payloads and lowest parsing cost thanks to binary encoding, which matters for high-volume internal traffic.
- GraphQL can reduce total latency for a screen by collapsing several round trips into one, even though each request does more work on the server.
- REST can be the fastest of all for read-heavy public data, because a CDN cache hit never reaches your servers.
Measure your own workload before choosing on performance alone. A well-cached REST endpoint and a poorly batched GraphQL resolver tell very different stories.
When to use each API style
When to use REST
Choose REST for public or partner APIs, simple CRUD services, and anything where cacheability and broad compatibility matter. Almost every language, tool, and developer already understands it, which lowers the cost of onboarding consumers.
When to use GraphQL
Choose GraphQL when you have several clients (web, iOS, Android, partner dashboards) that need different slices of the same connected data, and when frontend teams want to iterate without waiting on new backend endpoints. It works well as a backend-for-frontend layer that aggregates existing services.
When to use gRPC
Choose gRPC for service-to-service calls inside your platform, especially in polyglot environments, latency-sensitive paths, and streaming use cases. Strong contracts and generated clients reduce integration bugs between teams.
Can you combine REST, GraphQL, and gRPC?
Yes, and many mature systems do. A common layout looks like this:
- Internal microservices talk to each other over gRPC.
- A GraphQL gateway or backend-for-frontend aggregates those services for first-party apps.
- A REST API is exposed publicly for partners and third-party developers, often behind an API gateway.
Each style sits where its strengths matter most. The important part is keeping each boundary consistent so teams are not guessing which style a given service uses.
Key takeaways
- REST is the default for public, cacheable, resource-oriented APIs.
- GraphQL solves over-fetching and under-fetching for varied clients, but requires batching, query limits, and client-side caching.
- gRPC offers compact binary payloads, strict contracts, and native streaming, making it ideal for internal services.
- Performance usually depends more on caching and data access than on the API style itself.
- Mixing styles by boundary (internal, first-party, public) is a common and healthy pattern.
Frequently asked questions
Is GraphQL faster than REST?
Not inherently. GraphQL can reduce the number of round trips a client makes, which improves perceived speed for complex screens. REST can be faster for simple reads because responses are easy to cache at the CDN and browser level.
Can gRPC be used from a browser?
Not directly. Browsers do not expose the low-level HTTP/2 controls gRPC needs, so web apps use gRPC-Web with a proxy that translates requests. Many teams instead put a REST or GraphQL layer in front of gRPC services for browser clients.
Is REST outdated?
No. REST remains the most widely supported API style and is still the best choice for most public APIs. GraphQL and gRPC address specific problems rather than replacing REST everywhere.
Which API style is best for microservices?
gRPC is a strong default for synchronous calls between microservices because of its typed contracts and efficient encoding. REST is also common and simpler to debug. For decoupled workflows, consider asynchronous messaging instead of either.