How do you write a Golang server using gRPC from scratch? Heres how to do it.

Introduction

Before we begin, I’d like to mention that the code below was taken from the /example folder in the gRPC package.

As a textile learner, I like to write things out even if provided - I don’t feel confident being handed code and be expected to learn - I learn better by writing it from scratch; as a result, the gRPC documentation has not been good to me, to remedy this issue, I’ve written this article to help me understand better by writing out the steps from scratch.

Instructions

Let’s begin by going to our golang home directory.

$ cd ~/go/src/github.com/bartmika

Create our project and initialize our module.

$ mkdir simple-grpc
$ cd simple-grpc
$ go mod init github.com/bartmika/simple-grpc

Get our project dependencies

$ export GO111MODULE=on  # Enable module mode
$ go get google.golang.org/protobuf/cmd/protoc-gen-go
$ go get google.golang.org/grpc/cmd/protoc-gen-go-grpc

Before we begin, make sure we export our path (if you haven’t done this before).

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

To get started with gRPC we will need to setup our proto file. The file is responsible for defining the interface of our project. Proto is a short form for protocol buffers - Would you like to know more?

$ mkdir proto
$ vi proto/greeter.proto

Copy and paste the following code into the file you have open.

syntax = "proto3";

option go_package = "github.com/bartmika/simple-server"; // Don't forget to change!

package helloworld;

// The greeting service definition.
service Greeter {
    // Sends a greeting
    rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
    string name = 1;
}

// The response message containing the greetings
message HelloReply {
    string message = 1;
}

Now that we defined our interface, we’ll need to generate our client and server files.

$ protoc --go_out=. --go_opt=paths=source_relative \
   --go-grpc_out=. --go-grpc_opt=paths=source_relative \
   proto/greeter.proto

We have our server and client code generated. We can now write our own golang code to utilize them. Start by creating our server.

$ mkdir -p cmd/server
$ touch cmd/server/main.go

Copy and paste the following code into our server:

package main

import (
    "context"
    "log"
    "net"

    "google.golang.org/grpc"

    pb "github.com/bartmika/simple-grpc/proto"
)

const (
    port = ":50051"
)

// server is used to implement helloworld.GreeterServer.
type server struct {
    pb.UnimplementedGreeterServer
}

// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
    log.Printf("Received: %v", in.GetName())
    return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
}

func main() {
    lis, err := net.Listen("tcp", port)
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }
    s := grpc.NewServer()
    pb.RegisterGreeterServer(s, &server{})
    if err := s.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
}

Next create our client file.

$ mkdir -p cmd/client
$ touch cmd/client/main.go

Copy and paste the following code into our server:

package main

import (
    "context"
    "log"
    "os"
    "time"

    "google.golang.org/grpc"

    pb "github.com/bartmika/simple-grpc/proto"
)

const (
    address     = "localhost:50051"
    defaultName = "world"
)

func main() {
    // Set up a connection to the server.
    conn, err := grpc.Dial(address, grpc.WithInsecure(), grpc.WithBlock())
    if err != nil {
        log.Fatalf("did not connect: %v", err)
    }
    defer conn.Close()
    c := pb.NewGreeterClient(conn)

    // Contact the server and print out its response.
    name := defaultName
    if len(os.Args) > 1 {
        name = os.Args[1]
    }
    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    defer cancel()
    r, err := c.SayHello(ctx, &pb.HelloRequest{Name: name})
    if err != nil {
        log.Fatalf("could not greet: %v", err)
    }
    log.Printf("Greeting: %s", r.GetMessage())
}

Finally, run the server and client in seperate terminals via the commands:

$ go run cmd/server
$ go run cmd/client "Bart"