gRPC vs REST: Choosing an API Style
gRPC uses binary Protocol Buffers over HTTP/2 for fast, typed service calls; REST uses JSON over HTTP for accessible, resource-based APIs. How to pick.
gRPC and REST are two different philosophies for designing an API. REST models an API as a set of resources — users, orders, invoices — that clients fetch or modify with standard HTTP verbs and JSON. gRPC models an API as a set of remote procedure calls defined by a strict contract, serialized as compact binary Protocol Buffers and sent over HTTP/2. Neither is a strict upgrade of the other: REST optimizes for accessibility and broad compatibility, gRPC optimizes for speed and typed contracts between services you control on both ends.
How each one models communication
A REST API treats every entity as a resource with a URL, and reuses a small set of HTTP verbs to act on it: GET /users/123 reads a user, POST /users creates one, DELETE /users/123 removes one. The response body is usually JSON, readable by a human without any special tooling, and the “contract” between client and server is often informal — described in documentation or an OpenAPI spec, but not strictly enforced by the protocol itself.
gRPC instead treats the API as a set of function calls defined in a .proto file: rpc GetUser(UserRequest) returns (UserResponse). That file is the actual contract — a code generator turns it into client and server stubs in whatever languages the services are written in, so calling a remote service looks almost identical to calling a local function. The payload is Protocol Buffers, a compact binary format that’s smaller and faster to parse than JSON but not readable without decoding it first.
Wire format and performance
REST typically runs over HTTP/1.1 or HTTP/2 with JSON bodies — verbose but universally supported and easy to inspect with a browser or curl. gRPC requires HTTP/2 and uses binary Protocol Buffers, which are smaller on the wire and faster to serialize and deserialize. For high-throughput, latency-sensitive internal traffic — one microservice calling another thousands of times a second — that difference in encoding overhead adds up. For a handful of requests a user makes from a browser, it rarely matters.
Streaming
This is one of the sharpest differences. REST is fundamentally request-response: one request, one response, and anything resembling a live stream has to be bolted on with something like Server-Sent Events or a WebSocket. gRPC, because it’s built on HTTP/2’s multiplexed streams, supports four call patterns natively: unary (one request, one response, like REST), server streaming (one request, a stream of responses), client streaming (a stream of requests, one response), and full bidirectional streaming. If your service needs to push a continuous feed of updates back to a caller — telemetry, live pricing, progress on a long-running job — gRPC’s streaming support is built into the protocol rather than layered on top of it.
The browser problem
Browsers can’t speak raw gRPC. JavaScript’s fetch and XMLHttpRequest don’t give you the low-level control over HTTP/2 trailers that gRPC needs, so calling a gRPC service directly from client-side code isn’t possible without an intermediary — typically a gRPC-Web proxy that translates between a browser-friendly protocol and native gRPC on the backend. REST has no such problem; any browser has spoken HTTP and parsed JSON since forever. This alone rules gRPC out for public-facing browser APIs and keeps it mostly confined to server-to-server traffic.
Comparison table
| REST | gRPC | |
|---|---|---|
| Data format | JSON (text) | Protocol Buffers (binary) |
| Transport | HTTP/1.1 or HTTP/2 | HTTP/2 only |
| Contract | Informal, often OpenAPI | Strict .proto schema, code-generated |
| Streaming | Not native — needs SSE or WebSockets | Native, in all four directions |
| Browser support | Native | Requires a gRPC-Web proxy |
| Human readability | High — plain JSON | Low — binary, needs decoding |
| Best for | Public APIs, browser clients | Internal microservices, polyglot backends |
Where this leaves GraphQL
There’s a third option worth knowing about: GraphQL, which lets clients specify exactly which fields they want in a single request instead of hitting fixed REST endpoints. GraphQL solves a different problem than gRPC does — over-fetching and under-fetching of data for client-driven queries — and the two aren’t really in competition. It’s common to see gRPC used between internal services and REST or GraphQL exposed at the edge for external clients, often behind an API gateway that translates between the two.
When to reach for which
Pick REST when you’re building a public API, serving browser clients directly, or want an API anyone can explore with minimal tooling. Its ubiquity is the real advantage — every language, every platform, and every developer already knows how to talk to it.
Pick gRPC when you control both ends of the connection, typically inside a microservices architecture where dozens of internal services call each other constantly, in different languages, and the overhead of parsing JSON and the lack of a strict typed contract start to cost real latency and bugs. The code generation from .proto files also means client and server can’t silently drift out of sync the way a REST API and its documentation sometimes do.
Many production systems use both: gRPC for the internal service mesh, REST (or GraphQL) at the boundary where external clients and browsers connect in.
The takeaway
REST and gRPC solve the same broad problem — letting one program call another over a network — with different tradeoffs. REST trades some performance for universal compatibility and human readability; gRPC trades that readability and easy browser access for speed, native streaming, and a contract that’s enforced by tooling rather than convention. The choice usually comes down to who’s calling the API: the open internet favors REST, a fleet of internal services you control favors gRPC.
Tagged
Keep reading
Takina · · 4 min read REST vs GraphQL: Choosing an API Style
REST exposes fixed endpoints per resource; GraphQL lets clients query exactly the fields they need through one endpoint. How to choose between them.
Takina · · 4 min read What Is the Backend-for-Frontend (BFF) Pattern?
A backend-for-frontend (BFF) is a dedicated backend layer for one client type — shaping, aggregating, and simplifying calls to shared downstream APIs.
The Lycoris Team · · 4 min read API Versioning Strategies Explained
URI paths, custom headers, and content negotiation are the three common ways to version an API. Tradeoffs of each, and how to avoid breaking clients.