- Published on
A Dive Into gRPC and Protocol Buffers
- Authors
- Name
- Manuel Sousa
- @mlrcbsousa
As I continue to explore and deepen my understanding of modern web development, one technology that has particularly captured my interest is gRPC. It’s a high-performance, open-source framework developed by Google, designed to simplify and optimize the way our distributed systems communicate.
The What and Why of gRPC
If you've ever wondered how systems can efficiently communicate across different languages, platforms, or environments, gRPC (Google Remote Procedure Call) might be the answer. Designed to work over HTTP/2, gRPC brings advanced features such as bi-directional streaming, flow control, and header compression. This enables efficient, real-time communication between client and server applications.
Key Concepts of gRPC
gRPC isn't just another protocol; it's a whole ecosystem designed to handle modern communication needs. Here are some key features:
- Language Agnostic: gRPC supports a variety of programming languages, from Java and C++ to Go, Python, and Ruby, making it highly versatile for polyglot systems.
- Bi-directional Streaming: With built-in support for streaming, gRPC can handle real-time communication smoothly.
- Pluggable Architecture: Customize authentication, load balancing, or even message encoding as needed.
- Strongly Typed APIs: Using Protocol Buffers (protobufs), gRPC generates strongly typed APIs, helping to prevent errors and keep code clean.
- High Performance: Leveraging HTTP/2 and binary serialization with protobufs, gRPC offers reduced network overhead compared to traditional JSON over HTTP/1.1.
Why Use Protocol Buffers?
At the core of gRPC’s performance is its use of Protocol Buffers as the Interface Definition Language (IDL) and serialization format. Protocol Buffers are both efficient and language-neutral, making them ideal for use in modern microservices and performance-critical applications.
For those not familiar, Protocol Buffers are Google's data interchange format. Compared to JSON or XML, they’re more efficient in both size and speed. The result? Faster data exchange and lower network usage, especially for large-scale systems.
How to Implement gRPC in Your Projects
If you’re ready to jump in, let’s look at two potential use cases: Nest.js (for the backend) and Vue.js (for the frontend).
Using gRPC with Nest.js
Nest.js is a powerful framework for building scalable server-side applications. Integrating gRPC into a Nest.js project can be straightforward with the following steps:
Step 1: Install the necessary packages
npm install --save @nestjs/microservices @grpc/proto-loader grpc
Step 2: Define your service and messages in a .proto
file
syntax = "proto3";
package example;
service ExampleService {
rpc GetExampleData (ExampleRequest) returns (ExampleResponse);
}
message ExampleRequest {
int32 id = 1;
}
message ExampleResponse {
string message = 1;
}
Step 3: Set up the gRPC client and server in your Nest.js project
Configure the gRPC server in main.ts
:
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { grpcClientOptions } from './grpc-client.options';
async function bootstrap() {
const app = await NestFactory.createMicroservice(AppModule, grpcClientOptions);
await app.listen();
}
bootstrap();
Define the gRPC client options in grpc-client.options.ts
:
import { join } from 'path';
import { Transport } from '@nestjs/microservices';
export const grpcClientOptions = {
transport: Transport.GRPC,
options: {
package: 'example',
protoPath: join(__dirname, 'path/to/your/proto/file/example.proto'),
url: 'localhost:5000',
},
};
Step 4: Implement the service
import { Injectable } from '@nestjs/common';
@Injectable()
export class ExampleService {
getExampleData(request: { id: number }): { message: string } {
return { message: `Your request ID is: ${request.id}` };
}
}
Step 5: Create a controller to handle the gRPC methods
import { Controller } from '@nestjs/common';
import { GrpcMethod } from '@nestjs/microservices';
import { ExampleService } from './example.service';
@Controller()
export class ExampleController {
constructor(private readonly exampleService: ExampleService) {}
@GrpcMethod('ExampleService', 'GetExampleData')
getExampleData(request: { id: number }): { message: string } {
return this.exampleService.getExampleData(request);
}
}
Using gRPC with Vue.js
For browser-based gRPC communication, you can use the nice-grpc-web library. This library is a more modern and user-friendly gRPC-web client for TypeScript that works well with Vue.js.
Step 1: Install Dependencies
First, install nice-grpc-web
and google-protobuf
:
npm install --save @nice-grpc/web google-protobuf
Step 2: Generate TypeScript Code from Your .proto
File
Use the protoc
compiler with the grpc-web
plugin to generate TypeScript code:
protoc -I=. example.proto \
--js_out=import_style=commonjs:./generated \
--grpc-web_out=import_style=typescript,mode=grpcwebtext:./generated
This command generates the necessary JavaScript and TypeScript files in the ./generated
folder.
Step 3: Configure the gRPC Client with nice-grpc-web
Set up your gRPC client using nice-grpc-web
:
// src/grpc-client.ts
import { createChannel, createClient } from '@nice-grpc/web';
import { ExampleServiceDefinition } from './generated/ExampleService_pb_service';
const channel = createChannel('http://localhost:5000'); // Replace with your gRPC server URL
export const exampleServiceClient = createClient(ExampleServiceDefinition, channel);
Step 4: Use the Client in a Vue Component
Here's how to use the client in a Vue 3 component using the Composition API:
<template>
<div>
<button @click="fetchExampleData">Fetch Data</button>
<p>{{ message }}</p>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { exampleServiceClient } from '../grpc-client';
import { ExampleRequest } from './generated/example_pb';
const message = ref('');
const fetchExampleData = async () => {
try {
const request = new ExampleRequest();
request.setId(1); // Set your request parameters
const response = await exampleServiceClient.getExampleData(request);
message.value = response.getMessage();
} catch (error) {
console.error('Error fetching data:', error);
}
};
</script>
Using gRPC with Envoy
When working with gRPC in browser-based web applications, you face a significant limitation: browsers can’t directly communicate with gRPC servers. This is because gRPC uses the HTTP/2 protocol in a way that browsers don’t natively support. Specifically, browser clients can’t establish HTTP/2 connections in the manner required by gRPC.
To bridge this gap, we use a proxy like Envoy with the grpc_web
filter. The proxy translates browser-compatible HTTP/1.1 or HTTP/2 requests into standard gRPC requests, enabling seamless communication between browser-based clients and gRPC servers.
Setting Up Envoy Proxy
To make gRPC work from a browser client, you need to set up a proxy like Envoy. This ensures that the nice-grpc-web
client can interact with your backend gRPC service by translating gRPC-Web requests into standard gRPC requests.
Reference nice-grpc documentation.
Step 1: Create an Envoy Configuration File
First, create a file named envoy.yaml
with the following content:
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
codec_type: auto
stat_prefix: ingress_http
route_config:
name: local_route
virtual_hosts:
- name: local_service
domains: ['*']
routes:
- match: { prefix: '/' }
route:
cluster: service
timeout: 10s
max_stream_duration:
grpc_timeout_header_max: 10s
cors:
allow_origin_string_match:
- prefix: '*'
allow_methods: GET, PUT, DELETE, POST, OPTIONS
allow_headers: keep-alive,user-agent,cache-control,content-type,content-transfer-encoding,x-accept-content-transfer-encoding,x-accept-response-streaming,x-user-agent,x-grpc-web,grpc-timeout
max_age: '1728000'
expose_headers: grpc-status,grpc-message
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.cors
typed_config:
'@type': type.googleapis.com/envoy.extensions.filters.http.cors.v3.Cors
- name: envoy.filters.http.router
typed_config:
'@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
clusters:
- name: service
connect_timeout: 10.25s
type: logical_dns
http2_protocol_options: {}
lb_policy: round_robin
load_assignment:
cluster_name: service
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: backend-service
port_value: 50051
- Listener: Envoy listens on port
8080
and handles incoming HTTP/1.1 or HTTP/2 requests. - Filters:
grpc_web
: Translates gRPC-Web requests from the browser into standard gRPC requests.cors
: Manages cross-origin requests, essential for browser security.router
: Routes requests to the appropriate backend service.
- Cluster: Configures the backend gRPC service, which Envoy forwards requests to. In this example, the backend service is running on port
50051
.
Step 2: Set Up Docker Compose
To run Envoy alongside your gRPC backend service, use a docker-compose.yml
file:
version: '3.9'
services:
envoy:
image: envoyproxy/envoy:v1.28-latest
ports:
- '8080:8080'
volumes:
- ./envoy.yaml:/etc/envoy/envoy.yaml
backend-service:
image: your-backend-service-image
ports:
- '50051:50051'
- Envoy Service: Runs Envoy using the
envoyproxy/envoy
Docker image and exposes port8080
. - Backend Service: The gRPC backend service that Envoy forwards requests to. Replace
your-backend-service-image
with the actual image name of your backend service.
Wrapping Up
Using gRPC for web applications brings substantial performance and scalability benefits. However, due to browser limitations, a proxy like Envoy is essential for making gRPC-Web work seamlessly. By setting up an Envoy proxy, you ensure that your nice-grpc-web client can communicate effectively with your backend gRPC service.
This setup enables you to build high-performance, type-safe web applications using Vue 3’s Composition API and modern gRPC technology. Remember, the proxy configuration not only handles request translation but also manages CORS policies, making it a robust solution for web-based gRPC communication.
Check out this video on youtube for a visual intro to gRPC:
I hope this guide has provided clarity on how to set up a complete gRPC-Web architecture. If you have any questions or need further assistance, feel free to reach out. Happy coding!