gRPC vs REST vs GraphQL: Choosing the Right Protocol

Every time a new project kicks off, someone asks which of these three we should use, and I've learned to be suspicious of anyone who has one universal answer. I've shipped services in all three, sometimes on the same platform, and the right choice has always come down to who's calling the API and what they actually need, not which one is currently fashionable.
REST: still the right default for most things
For a public-facing or partner-integrated API, REST remains my default. It's understood by nearly every developer without explanation, works naturally with HTTP caching and load balancers, and its constraints, resources and verbs, are simple enough that a new consumer can guess their way through your API from the URL alone.
csharp
app.MapGet("/api/policies/{id}", async (Guid id, IPolicyService service) =>
{
var policy = await service.GetByIdAsync(id);
return policy is not null ? Results.Ok(policy) : Results.NotFound();
});
Where REST strains is exactly where its resource-shaped model doesn't match what the consumer needs: a mobile client wanting three fields from what would be five different REST resources.
GraphQL: for consumers with genuinely different data needs
I reach for GraphQL when I have multiple consumers with meaningfully different shapes of the same data, and REST would otherwise force heavy over-fetching or an ever-growing set of specialized endpoints.
graphql
query { customer(id: "123") { name policies { status premium } } }
The cost is real: you lose simple URL-based caching, rate limiting has to reason about query complexity, and N+1 doesn't disappear, it just moves into resolvers, needing DataLoader-style batching.
gRPC: for service-to-service calls where performance actually matters
This one gets underused, specifically because it's less familiar. For internal, high-throughput paths where you control both ends, gRPC's binary protocol buffers and HTTP/2 multiplexing give real latency advantages over JSON-over-HTTP.
protobuf
service PaymentService {
rpc ProcessPayment (PaymentRequest) returns (PaymentResponse);
}
On a fraud detection integration where every millisecond mattered, moving that internal call to gRPC produced a real, noticeable improvement. I wouldn't make that same call for a public-facing API, where gRPC's tooling friction outweighs the benefit for most consumers.
The question I actually ask
Not "which is best," because that has no stable answer. I ask: who is calling this, do I control both ends, and does this specific interaction have a performance or fetching-shape problem the default, REST, doesn't already solve well enough.
Mixing protocols is normal, not messy
A platform I worked on used REST for partner integrations, GraphQL for the mobile app's flexible needs, and gRPC for internal transaction-processing calls. That's matching each interaction to what it needs, not architectural indecision.
The real takeaway
The protocol debate gets treated like a religious argument online. In practice it's an engineering decision: understand your consumers, understand your constraints, pick the tool that fits. None of the three are wrong. They're answers to different questions.


