gRPC-Web lets you call gRPC services directly from a web browser, but it’s not a direct translation of gRPC; it’s a fundamentally different protocol designed for browser constraints.

Let’s see it in action. Imagine a simple Greeter service defined in protobuf:

syntax = "proto3";

package greet;

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}

A typical gRPC-Web setup involves a frontend JavaScript application, a gRPC-Web proxy, and your backend gRPC server.

Here’s how a browser client might initiate a call:

// Assuming you have generated gRPC-Web client code
const request = new proto.greet.HelloRequest({ name: "Browser" });
const client = new proto.greet.GreeterPromiseClient("http://localhost:8080"); // gRPC-Web proxy URL

client.sayHello(request).then(response => {
  console.log("Greeting:", response.message);
});

The browser doesn’t speak raw HTTP/2, which is gRPC’s native transport. Instead, gRPC-Web translates gRPC requests into HTTP/1.1 or HTTP/2 requests that browsers can handle. The key component enabling this is the gRPC-Web proxy.

The most common proxy is Envoy. Here’s a simplified Envoy configuration snippet for routing gRPC-Web traffic:

static_resources:
  listeners:
  - name: listener_0
    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
          stat_prefix: ingress_http
          route_config:
            virtual_hosts:
            - name: local_service
              domains: ["*"]
              routes:
              - match: { prefix: "/" }
                route:
                  cluster: grpc_backend
                  # This is crucial for gRPC-Web
                  upgrade_configs:
                  - enabled: true
                    upgrade_type: "websocket" # Or h2c for direct HTTP/2 if supported
          http_filters:
          - name: envoy.filters.http.grpc_web
            typed_config: {}
          - name: envoy.filters.http.router
            typed_config: {}
  clusters:
  - name: grpc_backend
    connect_timeout: 0.25s
    type: LOGICAL_DNS
    lb_policy: ROUND_ROBIN
    # Your actual gRPC server address
    load_assignment:
      cluster_name: grpc_backend
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address: { address: 127.0.0.1, port_value: 9090 }
    # gRPC specific configurations
    typed_extension_protocol:
      name: envoy.extensions.upstreams.http.v3.HttpProtocolOptions
      typed_config:
        "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
        explicit_http_config:
          http2_protocol_options: {}

The browser client sends a POST request to the gRPC-Web proxy (e.g., http://localhost:8080/greet.Greeter/SayHello). The Content-Type header will typically be application/grpc-web-text or application/grpc-web. The grpc_web filter in Envoy then translates this into a standard gRPC request (likely HTTP/2) to your backend service.

The upgrade_configs with upgrade_type: "websocket" is a common pattern. gRPC-Web can use WebSockets to stream messages, which is more efficient than repeatedly opening HTTP connections for bidirectional streaming. If your backend and proxy support HTTP/2 directly (h2c), you can configure Envoy to upgrade to that.

The gRPC-Web protocol itself is a bit of a hack. It serializes gRPC messages into a specific format that can be sent over HTTP/1.1 POST requests or WebSockets. The grpc_web filter in Envoy (or any compatible proxy) is responsible for both receiving these gRPC-Web formatted requests and converting them into native gRPC messages for the backend, and vice-versa for responses. The fact that it can use WebSockets for streaming is key; without it, streaming would be prohibitively expensive over typical browser HTTP semantics.

The next hurdle you’ll encounter is managing inter-service communication within your backend if you start building more complex microservices.

Want structured learning?

Take the full Grpc course →