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.
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"})
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.
Related articles
- Node.js Node.js gRPC Server Tutorial
Build a typed, high-performance gRPC server in Node.js with protobuf definitions, streaming RPCs, and production-ready patterns.
- Backend gRPC Introduction: Protobuf, Streaming, and Why
A working introduction to gRPC: protobuf schemas, code generation, the four RPC styles, deadlines, errors, and when to pick it over REST.
- Go Go Build Tags Explained
Use Go build tags to include or exclude files per OS, architecture, or custom condition. Learn the new //go:build syntax, common patterns, and how tags interact with the test runner.
- Go Go Context Cancellation Patterns
Master Go's context package: propagate deadlines, cancel goroutines safely, and avoid leaks with practical patterns for HTTP, database, and pipeline code.