gRPC-Web Should Have Fixed gRPC

ingve1 pts0 comments

gRPC-Web Should Have Fixed gRPC

Newer|Older

2026-07-14<br>&bull; 805 words<br>&bull; 4 minutes<br>&bull; #grpc<br>&bull; #grpc-web<br>&bull; #protobuf<br>&bull; #connectrpcgRPC-Web Should Have Fixed gRPC

gRPC-Web should have been the pressure that made gRPC simpler, more inspectable, and better suited for the web.

gRPC did a lot right. It turned Protocol Buffers into an API design tool, delivering typed messages, generated clients, and strict contracts. For backend networks where you control every hop, it is a fantastic tool.<br>But gRPC tied itself too tightly to its HTTP/2 transport model. Specifically, it built core application behavior, like delivering final application status codes via response trailers, around protocol features that browser JavaScript could not fully expose.<br>Browsers support HTTP/2 at the network layer, but frontend JavaScript gets a much narrower API. Standard browser tools like fetch and XHR do not expose HTTP trailers to application code in the way native gRPC depends on. This left gRPC in an awkward position: the browser was using HTTP/2 underneath, but JavaScript could not make a native gRPC call.<br>The Compromise: gRPC-Web<br>gRPC-Web was the official answer to this problem. It adjusted the wire format by encoding trailer-like data directly into the response body, allowing browser clients to function.<br>It worked, but the deployment story usually required a proxy, which added friction and complexity. Because ordinary gRPC servers did not speak this variant natively, you usually had to run Envoy or another gRPC-Web translation layer just to bridge browser requests into your backend.<br>We accepted a specialized protocol for backend-to-backend efficiency, but bringing gRPC to the web involved extra parts. gRPC-Web was treated as a weird browser variant on the side instead of a mandate to simplify gRPC itself.<br>Unary Calls Should Be Boring<br>The real mistake was letting the hardest engineering cases define the common case. Most RPCs are not bidirectional streams. They are ordinary request-response operations: create a thing, fetch a thing, update a thing.<br>For these unary calls, the protocol should have used standard HTTP semantics.<br>That means using HTTP’s existing machinery instead of recreating it inside a custom message envelope. Content-Type should say whether the body is JSON or binary protobuf. For successful unary calls, the response can use the same format as the request. Developers should be able to use application/json in browsers or local curl calls for easy debugging, while production workloads can use a binary protobuf content type for better performance. Accept-Encoding should advertise supported compression formats, and Content-Encoding should describe the compression actually used. When the body size is known, Content-Length can say how large it is. When it is not known ahead of time, the underlying HTTP version already has ways to determine where the body ends.<br>For unary RPCs, gRPC’s extra length prefix and compression flag were not elegant protocol design. They were streaming machinery leaking into the boring case.<br>You should still define your schema in protobuf, generate your clients, and get type safety. But the network layer should look like this:<br>curl \<br>-H 'Content-Type: application/json' \<br>-d '{"userId":"123"}' \<br>https://api.example.com/UserService/GetUser

If the operation naturally maps to an HTTP status, use it. If a user exists, return 200. If they do not, return 404. If the server crashes, return 500.<br>The standard counterargument from RPC purists is that HTTP status codes are too coarse. A 404 could mean the URL path is missing, or it could mean the requested database resource is missing. To avoid that ambiguity, gRPC often returns 200 OK for a successfully handled HTTP request and puts the RPC status in trailers.<br>But treating transport errors and application errors as completely separate worlds asks too much of the web. Load balancers, API gateways, CDNs, browser tools, and monitoring dashboards already understand 4xx and 5xx rates. Hiding application failures behind 200 OK makes that infrastructure less useful unless every layer becomes protocol-aware.<br>A web-native design should use standard HTTP status codes for coarse outcomes, then put richer domain-specific error details inside the JSON or binary response body.<br>Boring HTTP works. Browser dev tools understand it, standard middleboxes log it correctly, and tired engineers can debug it at 2 AM without specialized tools.<br>A practical protocol split would have been straightforward:<br>Unary calls: Standard HTTP request-response semantics.<br>Streaming calls: Framed messages over a stream-friendly transport.<br>The Web-Native Evolution<br>What I want from “the next version of gRPC” is not exotic. In fact, it already exists; it is exactly how ConnectRPC works. Connect preserves the protobuf service model and client generation, but treats unary calls as standard HTTP requests without the complexities of binary framing on top of HTTP. It proves that...

grpc http browser protocol application standard

Related Articles