9 Generating Go Code from a Protobuf File
Google Protocol Buffers (protobuf) has become one of the most popular serialization standards in the world of backend services. One of protobuf’s greatest strengths is its ability to generate code in a wide range of languages, including Go. In this chapter, we will take a detailed look at how to generate Go code from a .proto file, walk through its workflow, and share practical tips so that the process runs without a hitch.
Why Protobuf for Go?
Before diving into the technical details, it is important to understand the reasoning behind using protobuf in the Go world. Protobuf offers data serialization that is compact, type-safe, and high-performance. When we want to build microservices that communicate over gRPC within the Go ecosystem, protobuf + Go is a match made in heaven.
The Workflow for Generating Go Code from Protobuf
Let’s trace the main flow of generating Go code from a proto file using protoc.
flowchart TD
A[Menulis File proto] --> B[Menjalankan Protoc Compiler]
B --> C[Protoc Go Plugin]
C --> D[Menghasilkan File .pb.go]
Steps:
- We write the schema definition in a
.protofile - We run the
protoccompiler, equipped with the Go plugin - The protoc plugin transforms the
.protofile into a.pb.gofile that is ready to use in Go code
1. Preparing the Environment
Make sure our environment supports the following process:
| Tool/Library | Minimum Version | Purpose | Install Command |
|---|---|---|---|
| Go | 1.20+ | Primary language | go.dev/dl |
| Protobuf | 3.0+ | .proto compiler | Download Binaries |
| protoc-gen-go | 1.27+ | Go code generation plugin | go install google.golang.org/protobuf/cmd/protoc-gen-go@latest |
| protoc-gen-go-grpc | 1.2+ | Go gRPC code generation | go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest |
Make sure
GOPATH/bin is in your $PATH so that executables like protoc-gen-go can be recognized when running protoc.2. Example .proto File Definition
Let’s start with a simple proto file example, user.proto, representing a user management service:
1syntax = "proto3";
2
3package user;
4
5option go_package = "github.com/example/proto/userpb;userpb";
6
7message User {
8 int64 id = 1;
9 string name = 2;
10 string email = 3;
11}
12
13service UserService {
14 rpc GetUser(GetUserRequest) returns (User);
15}
16
17message GetUserRequest {
18 int64 id = 1;
19}Explanation:
- option go_package defines the Go import path and the name of the package that will be generated.
- There is one service,
UserService, with aGetUsermethod.
3. Generating the Go Code
Once the proto file is ready, it’s time to run protoc with the Go plugin. Let’s assume our proto file is located in the proto/ folder.
a) Generate the Data Structures
1protoc --go_out=. --go_opt=paths=source_relative proto/user.proto--go_out=.means the generated file (user.pb.go) will be placed in the current directory.--go_opt=paths=source_relativekeeps the folder structure the same as the input.
b) Generate the gRPC Code (Optional)
If there is a service (such as UserService), we will also want to generate its stub code:
1protoc --go-grpc_out=. --go-grpc_opt=paths=source_relative proto/user.protoAfterwards, two files will appear:
| File Name | Purpose |
|---|---|
| user.pb.go | Data structs, serialization logic |
| user_grpc.pb.go | gRPC stub/service interface |
4. Simulating Use of the Generated Files
Let’s simulate using the generated files in a Go application.
a) Import and Instantiate the Struct
1package main
2
3import (
4 "fmt"
5 userpb "github.com/example/proto/userpb"
6)
7
8func main() {
9 user := &userpb.User{
10 Id: 1,
11 Name: "Andi",
12 Email: "andi@mail.com",
13 }
14 fmt.Printf("User: %+v\n", user)
15}b) gRPC Server Handler Example
We can also directly implement a gRPC server from the generated code:
1type server struct {
2 userpb.UnimplementedUserServiceServer
3}
4
5func (s *server) GetUser(ctx context.Context, req *userpb.GetUserRequest) (*userpb.User, error) {
6 // Typically fetch the user data from the DB
7 return &userpb.User{
8 Id: req.GetId(),
9 Name: "Andi",
10 Email: "andi@mail.com",
11 }, nil
12}5. Troubleshooting: Common Errors During Generation
| Error Message | Cause | Solution |
|---|---|---|
protoc-gen-go: program not found or is not executable | Plugin not installed | Run go install ... and check $PATH |
option go_package is required | The go_package option is missing | Add option go_package to the .proto file |
cannot find package ... | Import structure doesn’t match | Stay consistent with go_package and $GOPATH |
6. Productivity Tips
- Save the generated files into a
internal/pb/subfolder to keep them isolated. - Add them to
.gitignoreif your build pipeline always regenerates them. - Update the plugins regularly, since the protobuf Go ecosystem is updated fairly often.
Automation Example
To be more productive, save the generation script into a Makefile:
1PROTO_DIR=proto
2OUT_DIR=internal/pb
3
4gen-proto:
5 protoc --go_out=$(OUT_DIR) --go_opt=paths=source_relative \
6 --go-grpc_out=$(OUT_DIR) --go-grpc_opt=paths=source_relative \
7 $(PROTO_DIR)/*.proto
7. Conclusion
Generating Go code from a Protobuf file is actually straightforward: write the schema, run protoc with the Go plugin, and use the result. Still, it pays to understand the workflow and the best practices so that the process is efficient, consistent, and trouble-free.
Protobuf + Go can serve as a strong foundation for modern microservices. With a solid grasp of code generation, your pipeline will become more reliable and future-proof.
Have fun exploring! 🚀
Don’t hesitate to share your experiences or ask questions if you run into an interesting error while using protobuf in Go.