4 Simple gRPC Project Structure in Go
gRPC is becoming increasingly popular as a modern Remote Procedure Call protocol of choice, especially for building efficient and scalable microservices systems. One of its key advantages is full support for a wide range of programming languages, high performance thanks to HTTP/2, and a reliable message serialization mechanism via Protocol Buffers (protobuf).
In this article, I’ll take a hands-on look at a simple gRPC project structure in Go (Golang). This discussion is a good fit for anyone just getting started with gRPC in Go, as well as developers who want to deepen their understanding of how to organize a project so it stays clean, scalable, and easy to extend.
Introduction: Why Should You Think About Project Structure?
We’re often tempted to dive straight into “tinkering” and cram everything into a single file. However, as the number of services grows, the project’s complexity grows along with it. A project that isn’t well structured becomes harder to maintain, harder to collaborate on as a team, and harder to scale.
A good project structure can:
- Separate code responsibilities (separation of concerns).
- Make testing and integration easier.
- Help onboard new developers.
- Speed up future service development.
Let’s take a look at the best practices.
Basic gRPC Project Structure in Go
The following structure is one of the standard, easy-to-understand approaches for a simple gRPC project. It’s enough to get started, yet flexible enough to evolve into something more complex down the road.
1grpc-simple-go/
2├── proto/
3│ └── hello.proto
4├── pb/
5│ └── hello.pb.go
6├── server/
7│ ├── handler.go
8│ └── main.go
9├── client/
10│ └── main.go
11├── go.mod
12└── README.mdLet’s go through each directory and file one by one:
| Directory/File | Brief Description |
|---|---|
proto/ | Holds the .proto files (protobuf definitions for the API contract) |
pb/ | Package of code generated from the proto files (usually auto-generated) |
server/ | gRPC server application implementation |
client/ | gRPC client application implementation |
go.mod | Go module file (dependency management) |
Step-by-step: Building the Project
1. Create the Protobuf Definition File
All gRPC communication starts from a .proto file. Here’s an example of the proto/hello.proto file:
1syntax = "proto3";
2
3package hello;
4
5service Greeter {
6 rpc SayHello (HelloRequest) returns (HelloReply);
7}
8
9message HelloRequest {
10 string name = 1;
11}
12
13message HelloReply {
14 string message = 1;
15}2. Generate Go Code from the Protobuf File
Install the protoc-gen-go and protoc-gen-go-grpc plugins (you can skip this if you’ve already installed them):
1go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
2go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latestGenerate the code:
1protoc --go_out=pb --go-grpc_out=pb proto/hello.protoThe generated files (hello.pb.go and hello_grpc.pb.go) automatically land in the /pb folder.
3. Implementing the gRPC Server
The server/ folder structure:
1server/
2├── handler.go
3└── main.goa. handler.go
This file contains the implementation of the service logic defined in the proto.
1// server/handler.go
2package server
3
4import (
5 context "context"
6 "grpc-simple-go/pb"
7)
8
9type GreeterServer struct {
10 pb.UnimplementedGreeterServer
11}
12
13func (s *GreeterServer) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloReply, error) {
14 return &pb.HelloReply{
15 Message: "Hello, " + req.Name + "!",
16 }, nil
17}b. main.go
The code to run the server:
1// server/main.go
2package main
3
4import (
5 "log"
6 "net"
7
8 "google.golang.org/grpc"
9 "grpc-simple-go/pb"
10 "grpc-simple-go/server"
11)
12
13func main() {
14 lis, err := net.Listen("tcp", ":8080")
15 if err != nil {
16 log.Fatalf("failed to listen: %v", err)
17 }
18
19 s := grpc.NewServer()
20 pb.RegisterGreeterServer(s, &server.GreeterServer{})
21
22 log.Println("Server listening on :8080...")
23 if err := s.Serve(lis); err != nil {
24 log.Fatalf("failed to serve: %v", err)
25 }
26}4. Implementing the gRPC Client
The client/ folder structure:
1client/
2└── main.goThe client code calls the SayHello method on the server.
1// client/main.go
2package main
3
4import (
5 "context"
6 "log"
7 "time"
8
9 "google.golang.org/grpc"
10 "grpc-simple-go/pb"
11)
12
13func main() {
14 conn, err := grpc.Dial("localhost:8080", grpc.WithInsecure())
15 if err != nil {
16 log.Fatalf("did not connect: %v", err)
17 }
18 defer conn.Close()
19
20 c := pb.NewGreeterClient(conn)
21
22 ctx, cancel := context.WithTimeout(context.Background(), time.Second)
23 defer cancel()
24 r, err := c.SayHello(ctx, &pb.HelloRequest{Name: "Gopher"})
25 if err != nil {
26 log.Fatalf("could not greet: %v", err)
27 }
28 log.Printf("Greeting: %s", r.GetMessage())
29}Execution Simulation
Suppose we run the server (go run server/main.go), then the client (go run client/main.go). The output on the client will look like this:
12024/06/08 Greeting: Hello, Gopher!Flow Diagram
To make things even clearer, here’s a diagram showing the request flow from the client to the gRPC server using Mermaid:
sequenceDiagram
participant Client
participant Server
Client->>Server: SayHello(HelloRequest)
Server-->>Client: HelloReply ("Hello, {name}!")
Explaining the Folders and Dependencies
| Folder | Notes |
|---|---|
proto/ | All protocol files. The single place to manage API contract changes. |
pb/ | Auto-generated! Don’t edit manually. Add to .gitignore if it gets too large. |
server/ | Service implementation, business logic, and server configuration |
client/ | A simulation or application that calls the gRPC service |
go.mod | Manages dependencies and tracks semantic versioning |
Best Practices
- Separate the proto files from the generated code: This makes CI/CD integration easier.
- Use a logic package: As the project scales, the business logic can be split out further into
/internal/handler/,/usecase/, and so on. - Document the communication flow: A sequence diagram like the one above is a great help for team collaboration.
Conclusion
The simple structure above is very easy to extend: want to add a service? Just add a new proto and handler. Want to separate the logic and delivery repositories? A little refactoring and you’re ready to go. With a clean structure like this, your Go project with gRPC will be far more maintainable and scalable.
Don’t forget, you can also check out various open source templates such as grpc-ecosystem or popular ones like golang-standards/project-layout for inspiration on more advanced structures.
Happy coding! 🚀
References: