Generating Code from .proto
The protoc compiler is the core tool that parses a .proto file and, on its own, generates plain message classes for serialization, but to get gRPC-aware client stubs and server base classes you need a language-specific gRPC plugin invoked alongside it, such as protoc-gen-go-grpc for Go or grpc_tools_node_protoc_plugin for Node.js. Running protoc with the right plugins turns your .proto definitions into a typed client you can call directly (client.GetUser(ctx, req)) and an abstract server interface you implement with your actual business logic, so the networking, serialization, and routing code is entirely generated and never hand-written.
Cricket analogy: protoc generating both message classes and gRPC stubs is like a groundstaff crew preparing both the pitch (data structures) and the stumps and boundary ropes (service scaffolding) from one match schedule, so players just show up and play rather than build the ground themselves.
The Build Command
A typical invocation names the .proto file, an --I or --proto_path flag pointing at the include directory so imports resolve, and one --*_out flag per plugin specifying where generated files should land, such as --go_out=. --go-grpc_out=. for Go, and teams almost always wrap this in a Makefile target, a Buf configuration, or a script rather than typing it by hand, because forgetting a flag or an import path silently produces stale or incomplete stubs. Many teams now use Buf instead of raw protoc directly, since it manages plugin versions, lints .proto style, and checks for breaking changes against a previous schema version automatically as part of CI.
Cricket analogy: Running the full protoc command by hand each time is like manually re-measuring the pitch length before every single delivery instead of trusting a standardized groundskeeping checklist — teams instead script it, the way a groundsman follows a fixed pre-match routine.
Implementing the Generated Server Interface
After generation, the server side gives you an interface or abstract base class with one method stub per RPC defined in the service, and your job is purely to implement business logic inside those methods: fetch from a database, call another service, and return (or stream) a populated response message, while the generated code handles decoding the incoming request and encoding your response back to bytes. This separation means regenerating code after a .proto change (say, adding a new field) never touches your hand-written business logic files, it only regenerates the message and stub files, so a well-organized project keeps generated code in its own directory, distinct from and never manually edited alongside your implementation code.
Cricket analogy: The generated server interface is like a pre-drawn fielding chart handed to a captain — the positions (method signatures) are already marked, and the captain's only job is deciding which specific fielder (business logic) stands in each spot.
# Generate Go message types and gRPC stubs from orders.proto
protoc \
--proto_path=proto \
--go_out=gen/go --go_opt=paths=source_relative \
--go-grpc_out=gen/go --go-grpc_opt=paths=source_relative \
proto/orders.proto
# Equivalent using Buf (buf.gen.yaml configures the plugins)
buf generate// Generated interface (from orders_grpc.pb.go) you implement:
type OrderServiceServer interface {
CreateOrder(context.Context, *CreateOrderRequest) (*CreateOrderResponse, error)
ListOrders(*ListOrdersRequest, OrderService_ListOrdersServer) error
}
// Your hand-written business logic:
func (s *orderServer) CreateOrder(ctx context.Context, req *pb.CreateOrderRequest) (*pb.CreateOrderResponse, error) {
order, err := s.db.InsertOrder(ctx, req.CustomerId, req.Lines)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to create order: %v", err)
}
return &pb.CreateOrderResponse{Order: order}, nil
}Buf (buf.build) has become a popular replacement for hand-invoking protoc directly: it pins plugin versions in a lockfile, runs style linting on .proto files, and can fail CI automatically if a change would break wire compatibility with a previously published schema.
Never hand-edit generated files (typically named *.pb.go, *_pb2.py, or similar) — they get overwritten on the next protoc run, so any business logic must live in separate files that import the generated types instead.
- protoc alone generates message classes; a gRPC plugin per language is needed for client stubs and server interfaces.
- A typical build specifies --proto_path for imports and one --*_out flag per plugin for generated output.
- Teams typically wrap protoc invocations in a Makefile, script, or Buf configuration rather than typing commands by hand.
- Buf adds plugin version pinning, .proto linting, and automated breaking-change detection in CI.
- Generated server code provides method stubs; developers implement only the business logic inside them.
- Generated files should never be hand-edited since they are overwritten on every regeneration.
- Keeping generated code in its own directory separate from hand-written logic avoids accidental edits and merge conflicts.
Practice what you learned
1. What does protoc generate on its own, without any additional plugin?
2. What is the purpose of the --proto_path (or -I) flag when running protoc?
3. What is a key benefit of using Buf instead of invoking protoc directly?
4. What should a developer do with a generated file like orders_grpc.pb.go?
Was this page helpful?
You May Also Like
Defining a .proto File
A practical guide to writing a .proto file: syntax declarations, messages, services, field rules, and naming conventions.
Protocol Buffers Explained
How Protocol Buffers serialize structured data into a compact binary format, and why gRPC uses them as its default wire format.
What Is gRPC?
An introduction to gRPC, the open-source high-performance RPC framework built on HTTP/2 and Protocol Buffers for connecting services across languages.
Related Reading
Related Study Notes in Web Development
Browse all study notesWebSockets Study Notes
Web Development · 30 topics
Web DevelopmentWebAssembly Study Notes
WebAssembly · 30 topics
Web DevelopmentSpring Boot Study Notes
Java · 30 topics
Web DevelopmentFlask Study Notes
Python · 30 topics
Web DevelopmentDjango Study Notes
Python · 30 topics
Web DevelopmentNext.js Study Notes
JavaScript · 30 topics