REST, GraphQL, and gRPC aren't ranked by quality — they're optimised for different callers and different constraints. Picking one because it's trendy, rather than because it fits how your API will actually be consumed, is how teams end up bolting a GraphQL layer onto a system that only ever needed three well-designed REST endpoints.
REST: the default for a reason
REST's strength is ubiquity — every language has a mature HTTP client, every developer already understands resources and status codes, and HTTP caching (ETags, Cache-Control) works for free at every layer of the stack from browser to CDN to reverse proxy. Its weakness shows up with nested or relational data: a mobile client that needs a user, their last five orders, and each order's line items either makes several round trips or the backend grows a bespoke aggregation endpoint for that one screen — the classic over-fetching/under-fetching problem REST doesn't solve on its own. REST is the right default for public APIs, CRUD-shaped resources, and anywhere HTTP caching semantics genuinely help.
GraphQL: when clients need to drive the response shape
GraphQL solves over-fetching directly — the client specifies exactly which fields it wants across however many related resources, in one request, and the server resolves them. This is a genuine win for frontend teams shipping multiple client types (web, iOS, Android) against one backend, where each client wants a different slice of the same data. The costs are real too: a naive resolver implementation causes the N+1 query problem (fetching a list, then querying once per item for its relations), which needs batching solutions like DataLoader to fix; HTTP caching mostly stops working because everything's a POST to one endpoint; and a single complex query can be an unintentional denial-of-service vector if you don't add query depth and complexity limits.
query OrderSummary($userId: ID!) {
user(id: $userId) {
name
orders(last: 5) {
id
total
lineItems {
sku
quantity
}
}
}
}
gRPC: internal service-to-service performance
gRPC uses HTTP/2 and Protocol Buffers instead of JSON over HTTP/1.1, which gives it a real performance edge — binary serialisation is smaller and faster to parse, and HTTP/2 multiplexing avoids head-of-line blocking. It also generates strongly-typed client and server code from a single .proto contract, which is a genuine productivity win for internal microservices maintained by teams who control both ends. The tradeoff: it's a poor fit for public or browser-facing APIs, since browsers can't speak gRPC natively without a proxy layer (grpc-web), and the binary format is opaque to casual debugging with curl in a way JSON never is.
The decision to adopt gRPC is almost always about service-to-service traffic inside your own infrastructure, where you control both client and server codegen. It rarely makes sense as the public-facing contract for third-party consumers.
The decision in practice
Public API, third-party consumers, resource-shaped data, want HTTP caching to just work: REST. Multiple client types with genuinely different data needs against a shared backend, frontend team owns the pain of over/under-fetching: GraphQL, with resolver-level batching from day one. Internal service mesh, both ends under your control, latency and throughput matter more than human readability: gRPC. Many real systems run all three at once — REST or GraphQL at the edge for clients, gRPC between internal services — because the decision is per-boundary, not company-wide.
Over-fetching is often a symptom of resources that don't match how clients actually use the data, not a fundamental limitation of REST. A well-designed set of purpose-built REST endpoints frequently solves the same problem GraphQL was reached for, with far less operational complexity.
| Style | Strongest for | Weak point |
|---|---|---|
| REST | Public APIs, HTTP caching, simplicity | Over/under-fetching on nested data |
| GraphQL | Multiple clients, client-driven shape | N+1 queries, cache complexity, query cost limits |
| gRPC | Internal service-to-service, performance | Poor browser support, opaque wire format |
Wrapping up
Choose based on who's calling and what they need from the response shape, not on which style is having a moment. REST remains the sound default for anything public; reach for GraphQL when client-driven shaping earns its resolver complexity, and for gRPC when the calls never leave your own infrastructure.
Independent software engineer in Nairobi specialising in Acumatica customisations, Laravel backends, and tax fiscalisation integrations across East and Southern Africa.