How To: Build a gRPC Server In Go

Pascal Allen
4 min readSep 27, 2023

--

In this publication I will demonstrate how a Go client can directly call a method on a Go server using gRPC.

Prerequisites

gRPC is a framework for Remote Procedure Calls, or RPCs, with a wide variety of supported languages. In gRPC there are 3 core concepts: protocol buffers, servers, and stubs. Let’s look at each concept in practice below.

Protocol Buffer Compiler Installation

The first thing we need to do is install the protocol buffer compiler. Visit https://github.com/protocolbuffers/protobuf/releases in your browser and download the zip file that corresponds to your OS and computer architecture.

Next, unzip the file under $HOME/.local by running the following command, where protoc-24.3-osx-universal_binary.zip is the zip file that corresponds to your OS and computer architecture:

unzip protoc-24.3-osx-universal_binary.zip -d $HOME/.local

Now update your environment’s PATH variable to include the path to the protoc executable by adding the following code to your .bash_profile or .zshrc file:

export PATH="$PATH:$HOME/.local/bin"

Note: If your .bash_profile or .zshrc file already contains an export path, you can simply append :$HOME/.local/bin.

Protocol Buffer Go Plugin Installation

Now that we have the protocol buffer compiler installed, protoc, we need to install a couple of plugins so that our protocol buffer code works with the Go runtime.

So, from a new terminal window run the following command:

go install google.golang.org/protobuf/cmd/protoc-gen-go@latest \
&& go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

And once more, update your environment’s PATH variable to include the path to the compiled Go executables by adding :$(go env GOPATH)/bin to your .bash_profile or .zshrc file. Your PATH variable should now look something like this:

export PATH="$PATH:$HOME/.local/bin:$(go env GOPATH)/bin"

Great! We’re now ready to build our project.

Project Initialization

Let’s start by creating a new directory for our project, initialize our Go module, and install Go dependencies:

mkdir grpc-go \
&& cd grpc-go \
&& go mod init github.com/pascalallen/grpc-go \
&& go mod tidy

Now from the root of the project, let’s create 3 subdirectories:

mkdir client helloworld server

These 3 subdirectories will contain our Go client application, Go server application, and our Hello World gRPC service.

cd into the helloworld directory and create a file called helloworld.proto at the root of the project and add the following code:

syntax = "proto3";

option go_package = "github.com/pascalallen/grpc-go/helloworld";

package helloworld;

service HelloWorldService {
rpc SayHello(HelloWorldRequest) returns (HelloWorldResponse) {}
}

message HelloWorldRequest {}

message HelloWorldResponse {
string message = 1;
}

This code defines our gRPC service and the method request and response types using protocol buffers. We’ll use this file to generate our gRPC server and stub interfaces.

From the helloworld directory, run the following command:

protoc --go_out=. --go_opt=paths=source_relative \
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
helloworld.proto

This command generates 2 files for us in the helloworld directory:

  • helloworld.pb.go, which contains all the protocol buffer code to populate, serialize, and retrieve request and response message types
  • helloworld_grpc.pb.go, which contains an interface type (or stub) for clients to call with the methods defined in the HelloWorldService service, and an interface type for servers to implement, also with the methods defined in the HelloWorldService service

Let’s keep going!

Server Implementation

Now that we’ve installed the protocol buffer compiler, protocol buffer plugins for Go, and initialized our project, we’re ready to create our Go server application and Go client application. Let’s start with the server by cding into the server directory and creating a file called main.go with the following code:

package main

import (
"context"
pb "github.com/pascalallen/grpc-go/helloworld"
"google.golang.org/grpc"
"log"
"net"
)

type server struct {
pb.UnimplementedHelloWorldServiceServer
}

func (s *server) SayHello(ctx context.Context, in *pb.HelloWorldRequest) (*pb.HelloWorldResponse, error) {
return &pb.HelloWorldResponse{Message: "Hello, World! "}, nil
}

func main() {
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("failed to listen on port 50051: %v", err)
}

s := grpc.NewServer()
pb.RegisterHelloWorldServiceServer(s, &server{})
log.Printf("gRPC server listening at %v", lis.Addr())
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}

This code implements the service interface generated from our service definition in helloworld.proto and provisions a gRPC server and listens to requests on port 50051 from clients. But not quite yet since we still need to implement our Go client application. So let’s keep going.

Client Implementation

Similar to what we did in the step above, let’s create our Go client application by cding into the client directory and creating a file called main.go with the following code:

package main

import (
"context"
pb "github.com/pascalallen/grpc-go/helloworld"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"log"
"time"
)

func main() {
conn, err := grpc.Dial("localhost:50051", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("failed to connect to gRPC server at localhost:50051: %v", err)
}
defer conn.Close()
c := pb.NewHelloWorldServiceClient(conn)

ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()

r, err := c.SayHello(ctx, &pb.HelloWorldRequest{})
if err != nil {
log.Fatalf("error calling function SayHello: %v", err)
}

log.Printf("Response from gRPC server's SayHello function: %s", r.GetMessage())
}

This code creates a gRPC channel to communicate with the gRPC server, creates a gRPC stub (client) to perform RPCs, and finally calls our SayHello function on the gRPC server.

In Action

Now that we’ve created our Go server and Go client, let’s see it in action by first spinning up the Go server. From the root of the project, run the following command to start the gRPC server:

go run server/main.go

And finally in a separate terminal window, run the following command to execute the gRPC client request:

go run client/main.go

The output in your client terminal window should look something like the following:

2023/09/27 15:00:51 Response from gRPC server's SayHello function: Hello, World!

And there you have it! Your first gRPC server and client implementation.

Conclusion

My goal for this publication was to contribute meaningful documentation to the gRPC and Go community. This was a very high-level tutorial and doesn’t detail everything. For more information on gRPC and Go, I recommend visiting https://grpc.io/docs/languages/go/. Have a great day!

--

--