How To: Build a gRPC Server In Go
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 typeshelloworld_grpc.pb.go
, which contains an interface type (or stub) for clients to call with the methods defined in theHelloWorldService
service, and an interface type for servers to implement, also with the methods defined in theHelloWorldService
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 cd
ing 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 cd
ing 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!