5 Setting Up Protocol Buffers (protobuf) for gRPC
If you’re building modern microservices with gRPC, one of the most important foundations you’ll touch repeatedly is Protocol Buffers (protobuf). This article is the fifth part of the gRPC introduction series, and we’ll focus on how to set up protobuf to be used as the communication interface in a gRPC system. I’ll walk you through everything from the concepts and setup to an implementation example along with a simulation of client-server communication.
Why Protocol Buffers?
Before we write any code, let’s first understand the reasoning. Protocol Buffers is a data serialization format developed by Google that offers speed, efficiency, and broad language support. Unlike JSON or XML, protobuf produces far more compact data and parses much faster — the main reason this technology became the backbone of gRPC.
A Quick Comparison
| Data Format | Data Size | Serialization Speed | Supported Languages |
|---|---|---|---|
| JSON | Large | Slow | All |
| XML | Very large | Slow | All |
| Protocol Buffers | Small | Very fast | Many |
Protocol Buffers Fundamentals
Before setting up protobuf, understand its basic format: the .proto file. Inside this file, we define:
- Messages: like a data schema (similar to a class)
- Services: the operations available for gRPC
A simple .proto gRPC example:
1syntax = "proto3";
2
3package user;
4
5service UserService {
6 rpc GetUser (GetUserRequest) returns (GetUserResponse);
7}
8
9message GetUserRequest {
10 string user_id = 1;
11}
12
13message GetUserResponse {
14 string user_id = 1;
15 string username = 2;
16 string email = 3;
17}Steps to Set Up Protobuf
Let’s break down, step by step, the protobuf setup for a microservice project using gRPC. For this tutorial, we’ll use Go and Python, but the workflow is nearly identical in other languages.
1. Install the Protocol Buffers Compiler (protoc)
Go
1brew install protobuf # MacOS 2sudo apt-get install protobuf-compiler # Debian/UbuntuWindows Download from https://github.com/protocolbuffers/protobuf/releases , then add it to your PATH.
2. Install the gRPC and Protobuf Plugins
Go:
1go install google.golang.org/protobuf/cmd/protoc-gen-go@latest 2go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest 3export PATH="$PATH:$(go env GOPATH)/bin"Python:
1pip install grpcio grpcio-tools
3. Write the .proto File
Create a file named user.proto. Write the service and message schema as shown in the example above. This file is the source of truth for both the client and the server.
1📂 project-root/
2 ┣ 📁 proto/
3 ┃ ┗ 📄 user.proto4. Generate the Source Code
This generated code becomes the bridge between the client, the server, and the protobuf interface.
Go:
1cd project-root 2protoc --go_out=. --go-grpc_out=. ./proto/user.protoThis will produce the files:
proto/user.pb.goproto/user_grpc.pb.go
Python:
1python -m grpc_tools.protoc -I./proto --python_out=. --grpc_python_out=. ./proto/user.proto
Workflow Diagram
To make things clearer, here is a flow diagram (using Mermaid) of the protobuf setup process for gRPC.
graph LR
A[Tulis file user.proto] --> B[Generate kode dengan protoc]
B --> C{Client / Server}
C --> D[Implementasikan logic bisnis spesifik]
C --> E[Gunakan class/method hasil generate]
Implementation Example: gRPC Server (Go)
Let’s see how the generated file we created earlier is used to build the server:
1// main.go
2package main
3
4import (
5 "context"
6 "log"
7 "net"
8
9 pb "project-root/proto"
10 "google.golang.org/grpc"
11)
12
13type server struct {
14 pb.UnimplementedUserServiceServer
15}
16
17func (s *server) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.GetUserResponse, error) {
18 log.Printf("Received GetUser request for: %s", req.UserId)
19 return &pb.GetUserResponse{
20 UserId: req.UserId,
21 Username: "johndoe",
22 Email: "john@example.com",
23 }, nil
24}
25
26func main() {
27 lis, err := net.Listen("tcp", ":50051")
28 if err != nil {
29 log.Fatalf("Failed to listen: %v", err)
30 }
31 grpcServer := grpc.NewServer()
32 pb.RegisterUserServiceServer(grpcServer, &server{})
33 log.Println("Server running at :50051")
34 grpcServer.Serve(lis)
35}Simulation: gRPC Client (Python)
1# client.py
2import grpc
3import proto.user_pb2 as user_pb2
4import proto.user_pb2_grpc as user_pb2_grpc
5
6def run():
7 with grpc.insecure_channel('localhost:50051') as channel:
8 stub = user_pb2_grpc.UserServiceStub(channel)
9 response = stub.GetUser(user_pb2.GetUserRequest(user_id="1234"))
10 print(f"User: {response.username}, Email: {response.email}")
11
12if __name__ == "__main__":
13 run()A Quick Walkthrough of the Communication Flow
- The client builds a request (
GetUserRequest) via the stub class (generated code) - That request is serialized by protobuf and sent over the network
- The server receives it at the gRPC endpoint and automatically deserializes it through the generated code
- The server fills in the response, serializes it again, and sends it back to the client
Table: Mapping .proto to Code
| .proto Element | Generated Go Output | Generated Python Output |
|---|---|---|
service UserService | UserServiceServer interface | UserServiceServicer class |
message | Go struct (e.g. GetUserRequest) | Python class (GetUserRequest) |
rpc GetUser | Interface method (Go) | Class method (Python) |
Pro Tips
- Use a dedicated folder (
/proto,/pb) for your.protofiles and generated code to keep the structure tidy. - Never edit the generated code by hand — the
.protofile is the only thing you should touch. - For multi-language projects, store the
.protofile in a separate repository as the API contract.
Conclusion
By setting up Protocol Buffers correctly, you’re one step closer to building a gRPC microservice ecosystem that is robust, cross-platform, and blazing fast. Protobuf simplifies maintenance, scaling, and system interoperability. In the next article, we’ll continue by fully implementing a gRPC client and server.
Happy experimenting—and remember, in gRPC, communication is about contracts, not just strings going back and forth.