Building a Simple gRPC Client and Server in Go

In this article, we’ll walk through building a basic gRPC client-server application in Go. You’ll learn how to define your service using Protocol Buffers, generate Go code with protoc
, and set up both the server and client to communicate using gRPC.
Step 1: Install Required Tools
Before starting, make sure you have the following installed:
Go (>=1.18)
protoc
(Protocol Buffers compiler)Go plugins for protobuf and gRPC:
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
These plugins are used by the Protocol Buffers compiler (
protoc
) to generate Go code for your messages and services.protoc-gen-go
: Generates Go structs for your messages.protoc-gen-go-grpc
: Generates gRPC server and client interfaces in Go.
Step 2: Define the Service (user.proto
)
Let’s define a simple service where the client can fetch a user by ID.
syntax = "proto3";
package user;
option go_package = "./userpb";
message GetUserRequest {
int32 id = 1;
}
message GetUserResponse {
int32 id = 1;
string name = 2;
string email = 3;
}
service UserService {
rpc GetUser(GetUserRequest) returns (GetUserResponse);
}
.proto
files define:The structure of request and response messages.
The available service(s) and RPC methods.
rpc GetUser(...)
declares a remote method callable by the client.Field numbers (like
id = 1
) are important for serialization/deserialization efficiency.
Step 3: Generate Go Code from Proto
Run the following command:
protoc --go_out=. --go-grpc_out=. user.proto
This generates userpb/user.pb.go
and userpb/user_grpc.pb.go
.
Step 4: Implement the gRPC Server
// server.go
package main
import (
"context"
"log"
"net"
"google.golang.org/grpc"
"userpb"
)
type server struct {
userpb.UnimplementedUserServiceServer
}
func (s *server) GetUser(ctx context.Context, req *userpb.GetUserRequest) (*userpb.GetUserResponse, error) {
log.Printf("Received request for user ID: %d", req.GetId())
return &userpb.GetUserResponse{
Id: req.GetId(),
Name: "Raj Dama",
Email: "raj@example.com",
}, nil
}
func main() {
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
userpb.RegisterUserServiceServer(s, &server{})
log.Println("gRPC server is running on port 50051...")
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
These imports provide everything you need to implement and run a gRPC server.
"context"
is used for timeout/cancellation and passing metadata.
// Create a struct that implements the gRPC server interface
type server struct {
userpb.UnimplementedUserServiceServer
}
You must implement the methods defined in the
UserService
interface.Embedding
UnimplementedUserServiceServer
ensures forward compatibility if methods are added in the future.
// Implements the GetUser RPC logic
func (s *server) GetUser(ctx context.Context, req *userpb.GetUserRequest) (*userpb.GetUserResponse, error) {
log.Printf("Received request for user ID: %d", req.GetId())
// In a real-world app, you'd query a database here
return &userpb.GetUserResponse{
Id: req.GetId(),
Name: "Raj Dama",
Email: "raj@example.com",
}, nil
}
This is your actual business logic.
GetUser
receives a request with a user ID, and returns user info.Right now, it's hardcoded — but this is where you'd plug in your DB query.
Step 5: Implement the gRPC Client
// client.go
package main
import (
"context"
"log"
"time"
"google.golang.org/grpc"
"userpb"
)
func main() {
// Establish a connection with the gRPC server
conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure())
if err != nil {
log.Fatalf("Failed to connect: %v", err)
}
defer conn.Close()
// Create a client for the UserService
client := userpb.NewUserServiceClient(conn)
// Create a request object
req := &userpb.GetUserRequest{Id: 1}
// Set a timeout using context
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
// Call the GetUser RPC
res, err := client.GetUser(ctx, req)
if err != nil {
log.Fatalf("Error calling GetUser: %v", err)
}
// Log the response
log.Printf("User ID: %d\nName: %s\nEmail: %s", res.GetId(), res.GetName(), res.GetEmail())
}
grpc.Dial
: Connects to the server onlocalhost:50051
.WithInsecure()
: Skips TLS setup for simplicity (not for production).NewUserServiceClient
: Creates a client from the generated interface.context.WithTimeout
: Ensures the call doesn't hang indefinitely.GetUser
: Performs the remote procedure call.
Running the Code
1] Start the server:
go run server.go
2] In another terminal, run the client:
go run client.go
You should see the server log the request and the client log the received user data.
Conclusion
This example demonstrated a minimal working gRPC setup in Go. With just a .proto
file and a few lines of code, you can set up a fast, type-safe, and scalable communication layer for your services.
Want to go deeper? Try implementing:
Bidirectional streaming
Authentication via interceptors
Middleware for logging and metrics
gRPC brings modern performance and structure to service communication—perfect for microservices, internal APIs, and real-time systems.
Subscribe to my newsletter
Read articles from Raj directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
