If you work with microservices or high-performance internal APIs, chances are you have already encountered gRPC. Combined with Go, it is one of the most productive and efficient stacks for building distributed systems.
In this post we will walk through the essential steps to build a simple but complete gRPC service in Go.
1. Why gRPC?
Compared to traditional REST/JSON APIs, gRPC offers:
- Strongly typed contracts (Protocol Buffers)
- Efficient binary serialization
- Built-in support for code generation
- First-class streaming (unary, server streaming, client streaming, bidirectional)
- Excellent performance and low latency
- Native context and deadline propagation
These characteristics make it a great fit for internal service-to-service communication.
2. Defining the Contract (Protobuf)
Everything starts with a .proto file:
syntax = "proto3";
package user.v1;
option go_package = "github.com/leogsouza/examples/user/v1;userv1";
message User {
int64 id = 1;
string name = 2;
string email = 3;
}
message GetUserRequest {
int64 id = 1;
}
message GetUserResponse {
User user = 1;
}
service UserService {
rpc GetUser(GetUserRequest) returns (GetUserResponse);
}
Generate the Go code:
protoc --go_out=. --go-grpc_out=. user/v1/user.proto
3. Implementing the Server
package main
import (
"context"
"log"
"net"
"google.golang.org/grpc"
userv1 "github.com/leogsouza/examples/user/v1"
)
type server struct {
userv1.UnimplementedUserServiceServer
}
func (s *server) GetUser(ctx context.Context, req *userv1.GetUserRequest) (*userv1.GetUserResponse, error) {
// In a real service you would query a database here
user := &userv1.User{
Id: req.Id,
Name: "Leonardo Souza",
Email: "leo@example.com",
}
return &userv1.GetUserResponse{User: user}, nil
}
func main() {
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
userv1.RegisterUserServiceServer(s, &server{})
log.Println("gRPC server listening on :50051")
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
4. Implementing a Client
conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
client := userv1.NewUserServiceClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
resp, err := client.GetUser(ctx, &userv1.GetUserRequest{Id: 1})
if err != nil {
log.Fatalf("GetUser failed: %v", err)
}
log.Printf("User: %v", resp.User)
5. Important Production Considerations
Timeouts and Cancellation
Always pass a context with a timeout:
ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
defer cancel()
Error Handling
Use gRPC status codes instead of generic errors:
import "google.golang.org/grpc/status"
import "google.golang.org/grpc/codes"
return nil, status.Errorf(codes.NotFound, "user %d not found", req.Id)
Interceptors
Interceptors are the equivalent of middleware. Use them for:
- Logging
- Authentication
- Metrics
- Tracing
Health Checks & Reflection
Enable gRPC reflection and health checking for better observability and developer experience.
6. When to Choose gRPC vs REST
| Use gRPC when… | Prefer REST when… |
|---|---|
| Internal service-to-service calls | Public-facing APIs |
| You need high performance / low latency | Browser clients are the main consumer |
| You want strong contracts + streaming | Simple CRUD with wide client support |
Many modern systems use both: gRPC internally and REST (or gRPC-Gateway) externally.
Conclusion
gRPC + Go is a powerful combination for building efficient and reliable backend services. The strong typing, excellent performance, and first-class support for deadlines and cancellation make it particularly well suited for microservices architectures.
Key steps to remember:
- Define your contract with Protobuf
- Generate the code
- Implement the server (and respect context)
- Use proper status codes and timeouts
- Add interceptors for cross-cutting concerns
In future posts we can explore streaming RPCs, authentication, and more advanced production patterns.
Happy coding!