Golang gRPC tutorial: server, client, and Protocol Buffers example

Golang grpc example and grpc golang tutorial: proto3 service, golang grpc server and grpc server golang with Register and Serve, go grpc client and go grpc example with grpc.NewClient and transport credentials, grpc go example layout, and gRPC vs REST pointers.

Published

Updated

Read time 4 min read

Reviewed byDeepak Prasad

Golang gRPC tutorial: server, client, and Protocol Buffers example

This grpc golang tutorial walks through a small Todo CreateTodo API so you can reuse it as a golang grpc example or grpc golang example: one Protocol Buffer schema, a golang grpc server (grpc server golang) that listens on TCP, and a go grpc client (go grpc example) that calls the generated stub. If you compared grpc go example articles before, this one keeps the tree explicit, fixes common copy‑paste mistakes (wrong go_package, deprecated dial options, swapped descriptions of generated files), and points to a larger gRPC CRUD sample when you outgrow the minimal path.

Tested with Go 1.24 on Linux (commands shown; run protoc where it is installed).


gRPC and Protocol Buffers in Go

gRPC is an RPC framework: a .proto file declares messages and a service with rpc methods. The Go toolchain plugs in protoc-gen-go and protoc-gen-go-grpc so you get typed structs plus server registration helpers and a client constructor. The wire format is protobuf over HTTP/2. Compared to a typical REST API, gRPC trades human-readable JSON for compact binary messages and strong schemas; browser callers usually need gRPC-Web and a proxy.


Prerequisites

  1. A supported Go release for your project.
  2. The protoc compiler (installation).
  3. Go plugins for protoc (install once, ensure $(go env GOPATH)/bin is on PATH):
bash
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
export PATH="$PATH:$(go env GOPATH)/bin"

If you are new to modules, see getting started with Go.


Project layout

Create a module with proto, server, and client packages:

bash
mkdir grpc-todo && cd grpc-todo
go mod init example.com/grpc-todo
mkdir -p proto server client

Generated files stay next to todo.proto under proto/ when you use paths=source_relative as below.


Step 1: proto/todo.proto

Create proto/todo.proto:

protobuf
syntax = "proto3";

package proto;

option go_package = "example.com/grpc-todo/proto";

message NewTodo {
  string name = 1;
  string description = 2;
  bool done = 3;
}

message Todo {
  string name = 1;
  string description = 2;
  bool done = 3;
  string id = 4;
}

service TodoService {
  rpc CreateTodo(NewTodo) returns (Todo) {}
}

The go_package value must match the import path you use from server and client (here example.com/grpc-todo/proto). Field numbers are stable identifiers on the wire; do not reuse numbers once a message is shipped.

Generate Go from the repository root:

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

You should see proto/todo.pb.go (messages and marshaling) and proto/todo_grpc.pb.go (the TodoService server interface, RegisterTodoServiceServer, and NewTodoServiceClient).


Step 2: golang grpc server (server/main.go)

Embed UnimplementedTodoServiceServer so forward-compatible stubs compile, implement CreateTodo, then Serve on a listener. The earlier article text incorrectly wrote s.Server(lis); the correct call is s.Serve(lis).

go
package main

import (
	"context"
	"log"
	"net"

	pb "example.com/grpc-todo/proto"
	"github.com/google/uuid"
	"google.golang.org/grpc"
)

const addr = ":50051"

type todoServer struct {
	pb.UnimplementedTodoServiceServer
}

func (s *todoServer) CreateTodo(ctx context.Context, in *pb.NewTodo) (*pb.Todo, error) {
	log.Printf("CreateTodo name=%q", in.GetName())
	return &pb.Todo{
		Name:        in.GetName(),
		Description: in.GetDescription(),
		Done:        in.GetDone(),
		Id:          uuid.New().String(),
	}, nil
}

func main() {
	lis, err := net.Listen("tcp", addr)
	if err != nil {
		log.Fatalf("listen: %v", err)
	}
	s := grpc.NewServer()
	pb.RegisterTodoServiceServer(s, &todoServer{})
	log.Printf("golang grpc server listening on %v", lis.Addr())
	if err := s.Serve(lis); err != nil {
		log.Fatalf("serve: %v", err)
	}
}

Run go mod tidy after generation so go.sum includes google.golang.org/grpc, protobuf, and uuid.


Step 3: go grpc client (client/main.go)

Dial the server with explicit transport credentials. grpc.WithInsecure is obsolete; use credentials/insecure for local development only.

go
package main

import (
	"context"
	"log"
	"time"

	pb "example.com/grpc-todo/proto"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
)

const target = "localhost:50051"

func main() {
	conn, err := grpc.NewClient(target, grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		log.Fatalf("dial: %v", err)
	}
	defer conn.Close()

	c := pb.NewTodoServiceClient(conn)
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	res, err := c.CreateTodo(ctx, &pb.NewTodo{
		Name:        "Code review",
		Description: "Review feature branch",
		Done:        false,
	})
	if err != nil {
		log.Fatalf("CreateTodo: %v", err)
	}
	log.Printf("id=%s name=%s", res.GetId(), res.GetName())
}

Both processes use the same TCP port number: the server listens on :50051, the client targets localhost:50051. There is no separate “50052” port for this layout.

If grpc.NewClient is unavailable in an older grpc-go snapshot, use grpc.Dial(target, grpc.WithTransportCredentials(insecure.NewCredentials())) with the same import.


Step 4: Run the golang grpc example

From the module root:

bash
go mod tidy

Terminal A (server):

bash
go run ./server

Terminal B (client):

bash
go run ./client

You should see server logs for CreateTodo and client logs printing a new UUID id and the submitted name. Exact log lines depend on your machine and timing.


Summary

A practical grpc golang tutorial chains four pieces: a proto3 service, protoc output split between todo.pb.go (messages) and todo_grpc.pb.go (golang grpc server registration and client stub), a grpc.Server that calls Serve, and a go grpc client built with grpc.NewClient plus insecure.NewCredentials for local dev only. This matches searches for golang grpc server, grpc server golang, golang grpc example, grpc golang example, go grpc server, go grpc client, go grpc example, grpc go example, and grpc golang in intent—minimal wiring before you add TLS, metadata, streaming, or persistence. For a richer API, continue with gRPC CRUD with PostgreSQL.


References


Frequently Asked Questions

1. What is the minimal golang grpc example?

Define a service and rpc methods in a .proto file, run protoc with the Go and grpc plugins to generate *_pb.go and *_grpc.pb.go, implement the generated server interface, then dial the same address from a client stub.

2. How do I run a golang grpc server and go grpc client together?

Start the server process first so it listens on a TCP port, then run the client in another terminal against localhost:thatPort; both binaries share the same Go module import path for generated proto code.

3. Why is grpc.WithInsecure deprecated in Go gRPC?

Use grpc.WithTransportCredentials(insecure.NewCredentials()) from google.golang.org/grpc/credentials/insecure, or a TLS config in production, instead of the removed insecure dial pattern.

4. Which generated file is the golang grpc server registration?

protoc-gen-go-grpc emits *_grpc.pb.go with the Register…Server function and the …Client constructor; protoc-gen-go emits *_pb.go with message types and marshaling helpers.

5. How does this relate to a larger grpc golang CRUD API?

The same pattern scales: more rpc methods, persistence in handlers, and metadata/auth interceptors; a fuller API walkthrough is linked from the summary for Postgres-backed gRPC.
Antony Shikubu

Systems Integration Engineer

Highly skilled software developer with expertise in Python, Golang, and AWS cloud services.