Skip to content
Santekno.com | Tech Tutorials and Trends
EN
📖 0%
12 Jun 2025 · 5 min read ·Article 4 / 110
Go

4 Simple gRPC Project Structure in Go

IH
Ihsan Arif
Writer at Santekno · Backend Engineer

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:

  1. Separate code responsibilities (separation of concerns).
  2. Make testing and integration easier.
  3. Help onboard new developers.
  4. 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.

bash
 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.md

Let’s go through each directory and file one by one:

Directory/FileBrief 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.modGo 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:

proto
 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):

bash
1go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
2go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

Generate the code:

bash
1protoc --go_out=pb --go-grpc_out=pb proto/hello.proto

The 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:

bash
1server/
2├── handler.go
3└── main.go

a. handler.go

This file contains the implementation of the service logic defined in the proto.

go
 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:

go
 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:

text
1client/
2└── main.go

The client code calls the SayHello method on the server.

go
 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:

text
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:

MERMAID
sequenceDiagram
    participant Client
    participant Server

    Client->>Server: SayHello(HelloRequest)
    Server-->>Client: HelloReply ("Hello, {name}!")

Explaining the Folders and Dependencies

FolderNotes
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.modManages 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:

Related Articles

💬 Comments