73. gRPC-Web for Communicating with the Frontend (React/Vue)
73. gRPC-Web for Communicating with the Frontend (React/Vue)
In the modern era of web applications, the need for efficient and reactive communication between the frontend and backend is becoming increasingly important. Traditionally, REST-based APIs using JSON have been the primary choice. Today, however, more and more engineering teams are turning to gRPC as an alternative offering low latency and high efficiency thanks to Protobuf. That said, gRPC essentially runs on top of HTTP/2—which not all browsers support natively for gRPC. This is where gRPC-Web steps in as a bridging technology that allows browsers (React/Vue) to communicate with a gRPC backend.
In this article, I’ll cover the concept of gRPC-Web, how to configure it, and an example implementation on a React/Vue frontend that interacts with a gRPC backend. Let’s start with a brief introduction.
What Are gRPC and gRPC-Web?
gRPC in Brief
gRPC is an HTTP/2-based remote procedure call framework developed by Google. It uses Protocol Buffers (“Protobuf”) as its data serialization format, which is more compact and faster than JSON. gRPC is well known for its Contract First, Multi-language (Polyglot), and streaming (bidirectional as well as server/client streaming) capabilities.
gRPC’s Limitations in the Browser
Unfortunately, browsers today do not fully support the features required by the gRPC protocol, particularly HTTP/2 framing and headers. Even the gRPC package available for Node.js cannot be bundled into the browser. This is where gRPC-Web becomes the solution.
gRPC-Web: A Bridge for the Browser
gRPC-Web is a project that enables web applications (React, Vue, Angular, and so on) to call gRPC backend APIs. It provides a JavaScript library and a simple wire protocol over HTTP/1.1/2 with CORS, so that frontend applications can communicate with a gRPC backend.
gRPC-Web Architecture
Let’s visualize the architecture:
flowchart LR
A[React/Vue SPA
gRPC-Web Client] --protokol gRPC-Web--> B[gRPC-Web Proxy]
B --gRPC--> C[gRPC Backend Service]
- React/Vue SPA: The browser uses a gRPC-Web client to make requests.
- gRPC-Web Proxy: Tools such as Envoy or a standalone proxy translate HTTP/1.1 gRPC-Web requests into HTTP/2 gRPC to forward them to the gRPC backend.
- gRPC Backend Service: A microservice/backend runs the gRPC server.
A Simple Case Study: Todo Service
1. Define the Protobuf
Suppose we want to build a simple Todo application. Here’s the todo.proto file:
1syntax = "proto3";
2
3package todo;
4
5service TodoService {
6 rpc ListTodos (Empty) returns (TodoListResponse);
7 rpc AddTodo (AddTodoRequest) returns (TodoResponse);
8}
9
10message Empty {}
11
12message AddTodoRequest {
13 string title = 1;
14}
15
16message Todo {
17 int32 id = 1;
18 string title = 2;
19 bool completed = 3;
20}
21
22message TodoResponse {
23 Todo todo = 1;
24}
25
26message TodoListResponse {
27 repeated Todo todos = 1;
28}2. Generate the Stubs
Use the protoc-gen-grpc-web plugin to generate the JS/TS stubs:
1protoc -I=./proto todo.proto \
2 --js_out=import_style=commonjs:./frontend/src/proto \
3 --grpc-web_out=import_style=typescript,mode=grpcwebtext:./frontend/src/proto3. Set Up the gRPC Backend
Suppose we implement the backend in Node.js (or Go). The key point is that this backend runs the TodoService service on a specific port (for example, 50051). In this article, we’ll focus on bridging it to the frontend.
4. Deploy the gRPC-Web Proxy
Using Envoy (the most common approach):
An envoy.yaml config snippet:
1static_resources:
2 listeners:
3 - name: listener_0
4 address:
5 socket_address: { address: 0.0.0.0, port_value: 8080 }
6 filter_chains:
7 - filters:
8 - name: envoy.filters.network.http_connection_manager
9 typed_config:
10 "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
11 codec_type: AUTO
12 stat_prefix: ingress_http
13 route_config:
14 name: local_route
15 virtual_hosts:
16 - name: backend
17 domains: ["*"]
18 routes:
19 - match: { prefix: "/" }
20 route: { cluster: grpc_backend }
21 http_filters:
22 - name: envoy.filters.http.grpc_web
23 - name: envoy.filters.http.cors
24 - name: envoy.filters.http.router
25 clusters:
26 - name: grpc_backend
27 connect_timeout: 0.25s
28 type: logical_dns
29 http2_protocol_options: {}
30 lb_policy: round_robin
31 load_assignment:
32 cluster_name: grpc_backend
33 endpoints:
34 - lb_endpoints:
35 - endpoint:
36 address:
37 socket_address:
38 address: host.docker.internal
39 port_value: 50051Implementation on the Frontend
React: Client Example
Install Dependencies
1npm install grpc-web google-protobufImplement the Todo Client
src/App.tsx:
1import React, { useEffect, useState } from 'react';
2import { TodoServiceClient } from './proto/todo_grpc_web_pb';
3import { Empty, AddTodoRequest, Todo } from './proto/todo_pb';
4
5const client = new TodoServiceClient('http://localhost:8080', null, null);
6
7function App() {
8 const [todos, setTodos] = useState<Todo.AsObject[]>([]);
9 const [title, setTitle] = useState('');
10
11 useEffect(() => {
12 const req = new Empty();
13 client.listTodos(req, {}, (err, response) => {
14 if (response) setTodos(response.getTodosList().map(todo => todo.toObject()));
15 });
16 }, []);
17
18 const handleAdd = () => {
19 const req = new AddTodoRequest();
20 req.setTitle(title);
21 client.addTodo(req, {}, (err, response) => {
22 if (response) setTodos(old => [...old, response.getTodo()?.toObject()]);
23 setTitle('');
24 });
25 };
26
27 return (
28 <div>
29 <h1>Todo List (gRPC-Web)</h1>
30 <ul>
31 {todos.map(todo => (
32 <li key={todo.id}>
33 {todo.title} {todo.completed ? "✔" : ""}
34 </li>
35 ))}
36 </ul>
37 <input value={title} onChange={e => setTitle(e.target.value)} />
38 <button onClick={handleAdd}>Add Todo</button>
39 </div>
40 );
41}
42
43export default App;Vue: The Same Concept
Install the Dependency
1npm install grpc-web google-protobufImplementation in Vue
src/components/Todo.vue:
1<template>
2 <div>
3 <h1>Todo List (gRPC-Web)</h1>
4 <ul>
5 <li v-for="todo in todos" :key="todo.id">{{ todo.title }} {{ todo.completed ? "✔" : "" }}</li>
6 </ul>
7 <input v-model="title" />
8 <button @click="addTodo">Add Todo</button>
9 </div>
10</template>
11
12<script lang="ts">
13import { defineComponent, ref, onMounted } from 'vue';
14import { TodoServiceClient } from '../proto/todo_grpc_web_pb';
15import { Empty, AddTodoRequest, Todo } from '../proto/todo_pb';
16
17const client = new TodoServiceClient('http://localhost:8080', null, null);
18
19export default defineComponent({
20 setup() {
21 const todos = ref<Todo.AsObject[]>([]);
22 const title = ref('');
23
24 onMounted(() => {
25 const req = new Empty();
26 client.listTodos(req, {}, (err, response) => {
27 if (response) todos.value = response.getTodosList().map(t => t.toObject());
28 });
29 });
30
31 const addTodo = () => {
32 const req = new AddTodoRequest();
33 req.setTitle(title.value);
34 client.addTodo(req, {}, (err, response) => {
35 if (response) todos.value.push(response.getTodo()?.toObject());
36 title.value = '';
37 });
38 };
39
40 return { todos, title, addTodo };
41 }
42});
43</script>How Does the Performance Compare to REST?
| Aspect | REST (JSON) | gRPC-Web (Protobuf) |
|---|---|---|
| Payload Size | Tends to be large (text) | Smaller (binary) |
| Serialization/Deserialization | Slow (JSON.parse) | Fast (Protobuf) |
| Auto-doc/Type-safety | Optional via Swagger/OpenAPI | Built-in via Proto |
| Streaming | Complicated (Server Sent Events) | Native in gRPC/limited on the Web* |
| Browser Support | Universal | Requires a Proxy (gRPC-Web) |
| CORS/Credential | Built-in | Requires config in the proxy |
* Note: gRPC-Web only supports unary and server streaming; client streaming is not yet supported.
Conclusion: Is gRPC-Web Right for You?
gRPC-Web unlocks the potential of using gRPC in a modern frontend stack (React/Vue). However, there are trade-offs:
Advantages:
- Small payloads, fast transmission.
- Automatic type-safety (from Protobuf to TypeScript/JS).
- Easy to integrate across multiple platforms.
Drawbacks:
- Setup and debugging are more complex (you need Envoy/a proxy).
- Streaming is not yet fully supported (non-bidirectional).
- Tooling issues: DevTools and error handling are not as clear as with plain fetch/AJAX.
So, if you need high API performance, type-safety, and you have a team familiar with Protobuf/gRPC, gRPC-Web is well worth trying in your React or Vue applications.
References
Happy experimenting, and don’t forget to explore! Share your experience using gRPC-Web in your next frontend project in the comments :)