API style choice — REST, gRPC, GraphQL — is less of a religious war than people make it. Each has a sweet spot. Picking by sweet spot avoids the 'we have gRPC because someone read a blog' anti-pattern.

Advertisement

REST — the default that mostly works

Resource-oriented, HTTP verbs, JSON. Universal client support, debuggable from curl, cache-friendly via HTTP. Right for: public APIs, third-party integrations, anywhere you don't control both ends. Versioning is awkward; over-fetching common.

gRPC — internal high-throughput

Protobuf + HTTP/2 + code-generated clients. Fast (binary, multiplexed). Strong typing across languages. Streaming support. Right for: internal service-to-service, performance-critical paths, polyglot orgs. Wrong for: browsers (gRPC-Web is workable but extra), public APIs (clients aren't ready).

Advertisement

GraphQL — client-driven queries

One endpoint; client specifies what to fetch. Eliminates over/under-fetching. Strong typing. Right for: complex client UIs (especially mobile with limited bandwidth) where queries vary across screens. Wrong for: simple CRUD (overkill), bandwidth-constrained backends (one big query can fan out to many internal calls), cache-friendly content (per-query caching is hard).

Versioning is the actual hard part

REST: header version, URL version, or 'we never break'. gRPC: protobuf field numbers + backward compatibility rules. GraphQL: schema deprecation. None of these is automatic — you must build for it from day 1 or pay later.

Error handling matters

HTTP status codes alone are insufficient (REST). gRPC has a status enum + details. GraphQL returns 200 with errors in body (controversial). Whatever you pick: distinguish 'client retriable', 'server retriable', and 'fatal'.

REST for public/CRUD/cacheable. gRPC for internal/typed/perf. GraphQL for client-driven UI. Design versioning and errors from day 1.