Skip to content
C Codeloom
Go

Go gRPC Tutorial

Build a gRPC service in Go from scratch: define protobufs, generate code, implement the server, write a client, and understand how streaming and interceptors fit together.

·4 min read · By Codeloom
Intermediate 9 min read

What you'll learn

  • What gRPC is and when to choose it
  • How to define services with Protocol Buffers
  • Generating Go code from .proto files
  • Implementing unary and streaming RPCs
  • Using interceptors and TLS

Prerequisites

  • Comfortable writing basic Go programs
  • Some exposure to HTTP APIs

gRPC is the default choice when two Go services need to talk to each other quickly and with strong typing. It uses HTTP/2, Protocol Buffers for serialization, and code generation to keep the wire and the language layer in sync. Once you have it set up, you stop writing JSON marshaling code and start describing methods.

What gRPC is and why use it

gRPC is a Remote Procedure Call framework. You describe services in a .proto file, run a code generator, and end up with strongly typed client and server stubs in any supported language. Compared to REST, gRPC gives you smaller payloads, multiplexed streams, bidirectional streaming, and explicit contracts.

The “why” is mostly about cost. Internal microservice traffic with gRPC is cheaper to serialize, easier to evolve, and far easier to refactor because the compiler points at every caller when you change a field.

Mental model

Think of gRPC as “function calls over the network with a schema”. Your .proto file is the source of truth. From it, you generate a server interface that you implement, and a client struct that you call. HTTP/2 sits underneath, but in day-to-day code you barely see it.

There are four call styles: unary (request, response), server streaming (one request, many responses), client streaming (many requests, one response), and bidirectional streaming. Each maps to a Go method signature shape.

Hands-on example

Define a small service in proto/echo.proto.

syntax = "proto3";
package echo;
option go_package = "example.com/echo;echopb";

service Echo {
  rpc Say (SayRequest) returns (SayResponse);
  rpc Stream (SayRequest) returns (stream SayResponse);
}

message SayRequest  { string text = 1; }
message SayResponse { string text = 1; }

Generate Go code with protoc --go_out=. --go-grpc_out=. proto/echo.proto. Then implement the server.

type server struct{ echopb.UnimplementedEchoServer }

func (s *server) Say(ctx context.Context, r *echopb.SayRequest) (*echopb.SayResponse, error) {
    return &echopb.SayResponse{Text: "echo: " + r.Text}, nil
}

func main() {
    lis, _ := net.Listen("tcp", ":50051")
    g := grpc.NewServer()
    echopb.RegisterEchoServer(g, &server{})
    g.Serve(lis)
}

A client looks like this.

conn, _ := grpc.Dial("localhost:50051", grpc.WithTransportCredentials(insecure.NewCredentials()))
c := echopb.NewEchoClient(conn)
resp, _ := c.Say(ctx, &echopb.SayRequest{Text: "hi"})
gRPC call path from client to server

Common pitfalls

The most frequent trap is forgetting to embed UnimplementedEchoServer in your server struct. Without it, adding a method to the proto breaks every existing server at compile time, which sounds nice until you realize protoc-gen-go-grpc actually requires the embed for forward compatibility.

Another is treating gRPC errors like generic Go errors. Use status.Error(codes.NotFound, "...") so clients can branch on status.Code(err) instead of string matching. Also, do not log and return; pick one, usually return.

Streaming methods need careful context handling. If the client cancels, the stream’s Context().Done() fires; you must stop sending. Long-lived streams without keepalive will get reaped by proxies.

Practical tips

Keep your .proto files in a shared repo or module so multiple services can import them. Version messages by adding fields, never renumbering. Reserve removed field numbers explicitly.

Use interceptors for cross-cutting concerns. Logging, auth, and metrics belong in grpc.UnaryInterceptor and grpc.StreamInterceptor, not sprinkled in every handler. The go-grpc-middleware collection has solid building blocks.

For development, grpcurl is the curl of gRPC and lets you poke services without writing a client. Enable reflection on dev servers so it works without proto files.

Wrap-up

gRPC trades a bit of setup ceremony for fast, typed, evolvable service-to-service calls. Define your proto carefully, embed the unimplemented servers, use status codes, and lean on interceptors. Once the generator is wired into your build, adding a new RPC is a matter of three lines in a proto and one function in Go.