gRPC-Web: Bringing gRPC to the Browser
Standard gRPC relies on HTTP/2 features that browsers do not fully expose — specifically, the ability to send HTTP/2 trailers and to access raw binary frames. gRPC-Web solves this by defining a protocol that works over HTTP/1.1 and HTTP/2 without requiring direct trailer support. ASP.NET Core has built-in middleware to serve gRPC-Web alongside regular gRPC, meaning you can use a single service implementation for both internal and browser-facing traffic.
The Problem
Browsers communicate through the Fetch and XMLHttpRequest APIs. Neither gives you the low-level HTTP/2 frame access that gRPC's wire protocol requires. Specifically:
- No support for HTTP/2 trailers (where gRPC sends status codes).
- No access to raw binary framing.
- CORS restrictions add further complications.
The gRPC-Web specification wraps gRPC messages in a format that works within these constraints. Trailers are encoded into the response body, and the binary payload is optionally base64-encoded.
Enabling gRPC-Web in ASP.NET Core
The Grpc.AspNetCore.Web package provides middleware that translates between gRPC-Web and native gRPC. No changes to your service implementation are needed.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddGrpc();
builder.Services.AddCors(options =>
{
options.AddDefaultPolicy(policy =>
{
policy.WithOrigins("https://myapp.example.com")
.AllowAnyHeader()
.AllowAnyMethod()
.WithExposedHeaders("Grpc-Status", "Grpc-Message", "Grpc-Encoding");
});
});
var app = builder.Build();
app.UseRouting();
app.UseCors();
app.UseGrpcWeb(); // Must be between UseRouting and MapGrpcService
app.MapGrpcService<WeatherForecasterService>()
.EnableGrpcWeb()
.RequireCors();
app.Run();
The key points:
UseGrpcWeb()adds the middleware that handles protocol translation.EnableGrpcWeb()on the service endpoint opts that service in.- CORS is essential — browser requests to a different origin will be blocked without it.
You can also enable gRPC-Web globally for all services instead of per-endpoint:
app.UseGrpcWeb(new GrpcWebOptions { DefaultEnabled = true });
Calling from JavaScript or TypeScript
On the browser side, the grpc-web npm package provides a client. You generate TypeScript stubs from your .proto file using protoc with the grpc-web plugin:
protoc -I=. weather.proto \
--js_out=import_style=commonjs:./generated \
--grpc-web_out=import_style=typescript,mode=grpcwebtext:./generated
Then call the service from your application:
import { WeatherForecasterClient } from './generated/WeatherServiceClientPb';
import { ForecastRequest } from './generated/weather_pb';
const client = new WeatherForecasterClient('https://api.example.com');
const request = new ForecastRequest();
request.setCity('Edinburgh');
request.setDays(3);
// Unary call
client.getForecast(request, {}, (err, response) => {
if (err) {
console.error(err.message);
return;
}
console.log(`${response.getCity()}: ${response.getTemperatureC()}C`);
});
// Server streaming
const stream = client.streamForecasts(request, {});
stream.on('data', (response) => {
console.log(`${response.getDate()}: ${response.getTemperatureC()}C`);
});
stream.on('error', (err) => {
console.error(err.message);
});
Note that gRPC-Web only supports unary and server streaming calls. Client streaming and bidirectional streaming are not available — the browser APIs simply cannot support them.
Calling from a .NET Client
You can also use gRPC-Web from a .NET client. This is useful when your client cannot use HTTP/2 — for example, in Blazor WebAssembly or behind certain proxies and load balancers that only support HTTP/1.1.
var handler = new GrpcWebHandler(new HttpClientHandler());
using var channel = GrpcChannel.ForAddress("https://api.example.com", new GrpcChannelOptions
{
HttpHandler = handler
});
var client = new WeatherForecaster.WeatherForecasterClient(channel);
var reply = await client.GetForecastAsync(
new ForecastRequest { City = "Glasgow", Days = 5 });
The GrpcWebHandler wraps a standard HttpClientHandler and translates calls to the gRPC-Web wire format automatically. This is the approach Blazor WebAssembly applications use, since the browser's networking stack does not expose HTTP/2 trailers.
Deployment Considerations
Most reverse proxies (NGINX, Envoy, Azure App Service) handle gRPC-Web without special configuration because it uses standard HTTP content types. However, keep these points in mind:
- Content types: gRPC-Web uses
application/grpc-web(binary) orapplication/grpc-web-text(base64-encoded). Ensure your proxy does not strip or rewrite these. - Timeouts: Streaming calls can be long-lived. Configure your proxy's request timeout accordingly.
- Compression: The gRPC-Web middleware supports gzip compression. Enable it if payload size is a concern.
When to Use gRPC-Web
gRPC-Web makes sense when you already have gRPC services and want to expose them to browser clients without building a separate REST layer. It eliminates the translation overhead of maintaining two API surfaces. For greenfield browser-facing APIs with no existing gRPC infrastructure, REST or GraphQL may be simpler starting points — but if gRPC is already your backbone, gRPC-Web is the pragmatic choice.