This episode opens gRPC up to the browser: the gRPC-Web concept and its limitations, integration with the Envoy proxy and API gateways, and combining gRPC with a REST gateway for hybrid APIs serving many consumers at once.

In episode 1 we mentioned: gRPC isn't for direct browser use. But what if a frontend has to talk to a gRPC service? The answer isn't abandoning gRPC — it's providing a bridge. Episode 16 covers three of those bridges: gRPC-Web (the browser protocol), Envoy (the translating proxy), and grpc-gateway (a REST gateway for hybrid APIs).
You'll understand why browsers can't use raw gRPC, how Envoy bridges the gap, and how a single .proto service serves gRPC, gRPC-Web, and REST consumers all at once.
Browsers can't use raw gRPC because of two limitations: JavaScript can't control the HTTP/2 flags (like trailer headers) that gRPC requires, and streaming responses on the client side aren't fully supported. gRPC-Web is the protocol that solves this — a version of gRPC that works in the browser via ordinary XHR or fetch.
The practical consequence: unary calls are fully supported, server streaming works (but as a single collected response), while client streaming and bidirectional streaming aren't available in gRPC-Web. Frontend design has to adapt.
With @grpc/grpc-js and @grpc/proto-loader, a browser client can simply use the same service:
import { GrpcWebFetchTransport } from "@protobuf-ts/grpcweb-transport";
const transport = new GrpcWebFetchTransport({
baseUrl: "https://api.example.com",
});
const client = new ProductServiceClient(transport);
const res = await client.getProduct({ id: "p-001" });
console.log(res.response.name);new GrpcWebFetchTransport({ baseUrl }) creates a gRPC-Web transport running on top of the browser's fetch. Note: the client above doesn't open a direct connection to the server — traffic still goes through a proxy.
Envoy is a production-grade proxy that understands gRPC. The browser sends gRPC-Web to Envoy, and Envoy translates it into full gRPC over HTTP/2 to the server. Envoy can be configured via the xDS control protocol (episode 10) or a static bootstrap file:
static_resources:
listeners:
- name: grpc_web_listener
address:
socket_address:
address: 0.0.0.0
port_value: 8080
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
codec_type: AUTO
http_filters:
- name: envoy.filters.http.grpc_web
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.grpc_web.v3.GrpcWeb
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
route_config:
virtual_hosts:
- name: grpc_service
domains: ["*"]
routes:
- match:
prefix: /
route:
cluster: catalog_grpcThe YAML config above declares a listener on port 8080, a grpc_web filter that translates the protocol, and a route to the catalog_grpc cluster. This route_config structure determines that all paths lead to the backend gRPC service.
Add a cluster definition to point Envoy at the gRPC server:
clusters:
- name: catalog_grpc
connect_timeout: 1s
type: STRICT_DNS
lb_policy: ROUND_ROBIN
http2_protocol_options: {}
load_assignment:
cluster_name: catalog_grpc
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: catalog
port_value: 50051http2_protocol_options: {} tells Envoy to use HTTP/2 toward the backend, and lb_policy: ROUND_ROBIN distributes traffic across instances. Envoy is now a single entry point for browsers and gRPC-Web clients.
grpc-gateway generates a REST proxy from the same .proto file, with the addition of a google.api.http annotation:
import "google/api/annotations.proto";
service CatalogService {
rpc GetProduct(ProductId) returns (Product) {
option (google.api.http) = {
get: "/v1/products/{id}"
};
}
}With option (google.api.http), the unary method GetProduct also becomes available as GET /v1/products/{id}. One contract yields two transports: gRPC for internal services, REST for public consumers.
go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@latest
protoc -I . \
--grpc-gateway_out=. \
--grpc-gateway_opt=paths=source_relative \
proto/catalog/v1/product.protoprotoc --grpc-gateway_out=. generates a proxy that accepts HTTP/JSON and calls the gRPC backend. This proxy reuses the same service, so there's no duplicated logic.
A common hybrid architecture: internal consumers use gRPC directly, browsers use gRPC-Web through Envoy, and external partners use REST through grpc-gateway. All paths end at the same gRPC server:
Browser -> Envoy -> gRPC server
Partner -> grpc-gateway -> gRPC server
Microservice -> gRPC -> gRPC serverThe benefit is clear: one .proto contract, one implementation, three consumers. API consistency is preserved because the gateway is a translator, not a reimplementation. The diagram above shows how the gRPC server becomes a single source of truth for every path.
Key takeaways:
google.api.http annotations in .proto.In episode 17 next, we cover CI/CD, GitOps, and production deployment — build pipelines for .proto compilation, code generation, and linting, contract validation with automated protobuf tests, and GitOps deployment for gRPC services on Kubernetes. The complete architecture now has to be built, tested, and deployed automatically.