Skip to content
Go back

Building a gRPC Service in Go - Server and Client Implementation

gRPC is a high-performance RPC framework that uses HTTP/2 for transport and Protocol Buffers for serialization. This guide walks through creating a functional gRPC server and client in Go.

Prerequisites

Before starting, install the following tools:

Protocol Buffer Compiler

macOS:

brew install protobuf

Linux:

apt install -y protobuf-compiler

Verify installation:

protoc --version
# Should output: libprotoc 3.x.x or higher

Go Protocol Buffer Plugins

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

Add the Go bin directory to your PATH if not already present:

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

Initialize Go Module

mkdir go-grpc
cd go-grpc
go mod init edgarmontano.com/go-grpc

Project Structure

go-grpc/
├── proto/
│   └── hello.proto
├── server/
│   └── main.go
├── client/
│   └── main.go
└── go.mod

Defining the Service with Protocol Buffers

Create proto/hello.proto:

syntax = "proto3";

option go_package = "edgarmontano.com/go-grpc/proto";

package hello;

// The Hello service definition.
service Hello {
  // 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;
}

The proto file defines:

Generating Go Code

Run the protocol buffer compiler to generate Go code:

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

This creates two files in the proto/ directory:

Server Implementation

Create server/main.go:

package main

import (
	"context"
	"log"
	"net"

	pb "edgarmontano.com/go-grpc/proto"
	"google.golang.org/grpc"
)

// server implements the Hello service
type server struct {
	pb.UnimplementedHelloServer
}

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

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

	s := grpc.NewServer()
	pb.RegisterHelloServer(s, &server{})

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

Server Components

Embedding UnimplementedHelloServer: The pb.UnimplementedHelloServer provides forward compatibility. If new methods are added to the service definition, the server continues to compile and returns “unimplemented” errors for methods not explicitly implemented.

SayHello Method: Implements the RPC defined in the proto file. Receives a HelloRequest, logs the name, and returns a HelloReply with a greeting message.

TCP Listener: The server listens on port 50051. This port can be changed based on your infrastructure requirements.

Server Registration: pb.RegisterHelloServer registers the service implementation with the gRPC server, making the RPC methods available to clients.

Client Implementation

Create client/main.go:

package main

import (
  "context"
  "log"
  "time"

  pb "edgarmontano.com/go-grpc/proto"
  "google.golang.org/grpc"
  "google.golang.org/grpc/credentials/insecure"
)

func main() {

  conn, err := grpc.NewClient("localhost:50051", grpc.WithTransportCredentials(insecure.NewCredentials()))
  if err != nil {
    log.Fatalf("did not connect: %v", err)
  }
  defer conn.Close()

  // Create a client
  c := pb.NewHelloClient(conn)

  // Call the SayHello method
  ctx, cancel := context.WithTimeout(context.Background(), time.Second)
  defer cancel()

	r, err := c.SayHello(ctx, &pb.HelloRequest{Name: "World"})
	if err != nil {
		log.Fatalf("could not greet: %v", err)
	}

	log.Printf("Greeting: %s", r.GetMessage())
}

Client Components

Connection Setup: grpc.NewClient establishes a connection to the server. The insecure.NewCredentials() option disables transport security (TLS). In production, use proper TLS credentials.

Client Creation: pb.NewHelloClient creates a client stub that provides methods corresponding to the service RPCs.

Context with Timeout: The context includes a 1-second timeout. If the RPC doesn’t complete within this time, it returns a deadline exceeded error.

RPC Invocation: Calls SayHello with a request message. The response is returned synchronously.

Installing Dependencies

go get google.golang.org/grpc
go get google.golang.org/protobuf

Run go mod tidy to clean up dependencies:

go mod tidy

Running the Application

Start the Server

go run server/main.go

Output:

server listening at [::]:50051

Run the Client

In a separate terminal:

go run client/main.go

Output:

Greeting: Hello World

The server terminal will show:

Received: World

Error Handling Considerations

The current implementation includes basic error handling. For production systems, consider:

Security in Production

The example uses insecure credentials for simplicity. Production deployments require:

// Server with TLS
creds, err := credentials.NewServerTLSFromFile("server.crt", "server.key")
if err != nil {
    log.Fatalf("failed to load TLS keys: %v", err)
}
s := grpc.NewServer(grpc.Creds(creds))

// Client with TLS
creds, err := credentials.NewClientTLSFromFile("ca.crt", "")
if err != nil {
    log.Fatalf("failed to load TLS cert: %v", err)
}
conn, err := grpc.NewClient("localhost:50051", grpc.WithTransportCredentials(creds))

Performance Characteristics

gRPC offers several advantages for service-to-service communication:


Share this post on:

Previous Post
Python 3.14 Free-Threading - True Parallelism Without the GIL
Next Post
Fast PDF Text Extraction for Embeddings - Switching from Unstructured to PyMuPDF