Applied Go

Building gRPC Services in Go

Learn how to build high-performance, type-safe APIs using gRPC and Protocol Buffers. gRPC uses HTTP/2 for efficient communication and supports streaming, making it ideal for microservices and real-time applications.

What is gRPC Protocol Buffers Service Definition Building a Server Building a Client Streaming Interview Questions

What is gRPC?

gRPC is a modern RPC framework built on HTTP/2 that enables efficient communication between services. Unlike REST APIs which use JSON over HTTP/1.1, gRPC uses Protocol Buffers (a binary format) over HTTP/2, resulting in smaller payloads and better performance.

gRPC vs REST

gRPC advantages: Binary protocol (smaller payloads), HTTP/2 multiplexing, built-in streaming, strongly typed with Protocol Buffers, better performance for high-throughput scenarios.

REST advantages: Simpler to understand, works with any HTTP client, better browser support, easier debugging with text-based JSON.

Use gRPC for: Microservices, real-time applications, high-throughput APIs.
Use REST for: Public APIs, web applications, simple CRUD operations.

Client
CreateTodo(req)
gRPC Server
returns Todo
Click to simulate a unary RPC call — one request, one response over HTTP/2.

Protocol Buffers

Protocol Buffers (protobuf) is Google's language-neutral, platform-neutral mechanism for serializing structured data. You define your data structures in a .proto file, then compile it to generate Go code.

todo.proto
syntax = "proto3";

package todo;

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

message Todo {
  string id = 1;
  string title = 2;
  bool completed = 3;
  int64 created_at = 4;
  int64 updated_at = 5;
}

message CreateTodoRequest {
  string title = 1;
}

message UpdateTodoRequest {
  string id = 1;
  string title = 2;
  bool completed = 3;
}

message GetTodosRequest {}

message TodoList {
  repeated Todo todos = 1;
}

Each field has a number (1, 2, 3, etc.) that identifies it in the binary format. The repeated keyword indicates a list. Compile this with:

Terminal
$ protoc --go_out=. --go-grpc_out=. todo.proto
field 1
string id
field 2
string title
field 3
bool completed
field 4
int64 created_at
Click a field to learn how Protocol Buffers encodes it.

Service Definition

Define your RPC methods in the same .proto file. Each method specifies its input and output message types.

todo.proto (continued)
service TodoService {
  // Unary RPC: single request, single response
  rpc CreateTodo(CreateTodoRequest) returns (Todo);
  rpc GetTodos(GetTodosRequest) returns (TodoList);
  rpc UpdateTodo(UpdateTodoRequest) returns (Todo);

  // Server streaming: single request, stream of responses
  rpc StreamTodos(GetTodosRequest) returns (stream Todo);

  // Client streaming: stream of requests, single response
  rpc CreateMultipleTodos(stream CreateTodoRequest) returns (TodoList);

  // Bidirectional streaming: stream of requests, stream of responses
  rpc SyncTodos(stream Todo) returns (stream Todo);
}

Building a Server

Implement the service interface generated by protoc. Each RPC method becomes a function that receives a context and the request message.

server.go
package main

import (
	"context"
	"log"
	"net"
	"google.golang.org/grpc"
	pb "github.com/example/todo-grpc/pb"
)

type TodoServer struct {
	pb.UnimplementedTodoServiceServer
	todos map[string]*pb.Todo
}

func (s *TodoServer) CreateTodo(ctx context.Context, req *pb.CreateTodoRequest) (*pb.Todo, error) {
	todo := &pb.Todo{
		Id:        "todo-1",
		Title:     req.Title,
		Completed: false,
		CreatedAt: int64(time.Now().Unix()),
		UpdatedAt: int64(time.Now().Unix()),
	}
	s.todos[todo.Id] = todo
	return todo, nil
}

func (s *TodoServer) GetTodos(ctx context.Context, req *pb.GetTodosRequest) (*pb.TodoList, error) {
	var todos []*pb.Todo
	for _, todo := range s.todos {
		todos = append(todos, todo)
	}
	return &pb.TodoList{Todos: todos}, nil
}

func (s *TodoServer) StreamTodos(req *pb.GetTodosRequest, stream pb.TodoService_StreamTodosServer) error {
	for _, todo := range s.todos {
		if err := stream.Send(todo); err != nil {
			return err
		}
	}
	return nil
}

func main() {
	listener, err := net.Listen("tcp", ":50051")
	if err != nil {
		log.Fatalf("Failed to listen: %v", err)
	}

	grpcServer := grpc.NewServer()
	pb.RegisterTodoServiceServer(grpcServer, &TodoServer{
		todos: make(map[string]*pb.Todo),
	})

	log.Println("gRPC server listening on :50051")
	if err := grpcServer.Serve(listener); err != nil {
		log.Fatalf("Failed to serve: %v", err)
	}
}

Building a Client

Create a client connection and call the RPC methods. The generated code handles all the serialization and network communication.

client.go
package main

import (
	"context"
	"log"
	"google.golang.org/grpc"
	pb "github.com/example/todo-grpc/pb"
)

func main() {
	// Connect to server
	conn, err := grpc.Dial(":50051", grpc.WithInsecure())
	if err != nil {
		log.Fatalf("Failed to connect: %v", err)
	}
	defer conn.Close()

	client := pb.NewTodoServiceClient(conn)
	ctx := context.Background()

	// Create a todo
	todo, err := client.CreateTodo(ctx, &pb.CreateTodoRequest{
		Title: "Learn gRPC",
	})
	if err != nil {
		log.Fatalf("CreateTodo failed: %v", err)
	}
	log.Printf("Created: %v", todo)

	// Get all todos
	list, err := client.GetTodos(ctx, &pb.GetTodosRequest{})
	if err != nil {
		log.Fatalf("GetTodos failed: %v", err)
	}
	log.Printf("Todos: %v", list.Todos)
}

Streaming

Client
Server
Select a streaming type to see how messages flow.

gRPC supports four types of streaming: unary (request-response), server streaming, client streaming, and bidirectional streaming. Streaming is useful for large datasets or real-time updates.

streaming_client.go
package main

import (
	"context"
	"log"
	pb "github.com/example/todo-grpc/pb"
)

// Server streaming: receive multiple responses
func streamTodos(client pb.TodoServiceClient) {
	stream, err := client.StreamTodos(context.Background(), &pb.GetTodosRequest{})
	if err != nil {
		log.Fatalf("StreamTodos failed: %v", err)
	}

	for {
		todo, err := stream.Recv()
		if err != nil {
			break // stream ended
		}
		log.Printf("Received: %v", todo)
	}
}

// Client streaming: send multiple requests
func createMultipleTodos(client pb.TodoServiceClient) {
	stream, err := client.CreateMultipleTodos(context.Background())
	if err != nil {
		log.Fatalf("CreateMultipleTodos failed: %v", err)
	}

	for i := 0; i < 3; i++ {
		stream.Send(&pb.CreateTodoRequest{
			Title: "Todo \" + string(rune('A'+i)) + "\",
		})
	}

	list, err := stream.CloseAndRecv()
	if err != nil {
		log.Fatalf("CloseAndRecv failed: %v", err)
	}
	log.Printf("Created todos: %v", list.Todos)
}
When to use streaming

Server streaming: Large result sets, real-time updates, log streaming.

Client streaming: Bulk uploads, batch operations, log collection.

Bidirectional streaming: Chat applications, collaborative editing, real-time synchronization.

Interview Questions

Common interview questions about gRPC and Protocol Buffers in Go — organized by experience level. Click a tab to filter.

Q1. What is gRPC?

gRPC is an open-source remote procedure call (RPC) framework, originally built at Google, that lets a client call a method on a server as if it were a local function. It runs over HTTP/2, uses Protocol Buffers as its default serialization format, and generates strongly-typed client and server code from a service definition — instead of hand-writing HTTP routes and JSON parsing, you call generated methods directly.

Q2. What is Protocol Buffers?

Protocol Buffers (protobuf) is a language-neutral, binary serialization format developed by Google. You define message shapes in a .proto file, and a compiler generates code to encode and decode those messages efficiently in your target language. It is more compact and faster to parse than JSON, at the cost of not being human-readable on the wire.

Q3. What is a .proto file?

A .proto file is a schema file written in Protocol Buffers' interface definition language (IDL). It declares message types (the data structures) and service definitions (the RPC methods), each field tagged with a unique number and type. This file is the single source of truth from which client and server code is generated for every supported language.

Q4. What does protoc / protoc-gen-go do?

protoc is the Protocol Buffers compiler; it parses a .proto file and invokes language-specific plugins to generate code. protoc-gen-go is the Go plugin that generates Go structs for each message and marshal/unmarshal methods, while protoc-gen-go-grpc generates the client stub and server interface for each service block.

Q5. What is a service definition in a proto file?

A service block in a .proto file declares one or more RPC methods, each specifying a request message type and a response message type, for example rpc GetUser(GetUserRequest) returns (GetUserResponse). The code generator turns this into a Go interface the server must implement and a client struct with matching methods.

Q6. What's the basic difference between gRPC and REST?

REST is an architectural style typically layered over HTTP/1.1 with JSON payloads and resource-oriented URLs (GET /users/1), while gRPC is a strict RPC contract layered over HTTP/2 with binary protobuf payloads and generated method calls. gRPC also has native support for streaming, whereas plain REST over HTTP/1.1 does not.

Q7. What is a client stub in gRPC?

A client stub is generated code that exposes the RPC methods declared in a service block as regular Go function calls on a struct, for example client.GetUser(ctx, req). Internally the stub serializes the request to protobuf, sends it over an HTTP/2 connection, and deserializes the response — the caller never touches wire-level details.

Q8. What transport protocol does gRPC use?

gRPC is built on top of HTTP/2. It relies on HTTP/2 features like multiplexed streams over a single TCP connection, binary framing, and header compression to support concurrent requests and long-lived streaming without opening a new connection per call.

Q9. What are the four types of RPC that gRPC supports?

gRPC supports unary RPC (one request, one response), server-streaming RPC (one request, a stream of responses), client-streaming RPC (a stream of requests, one response), and bidirectional streaming RPC (both sides stream independently over the same call). All four are declared in the .proto service definition using the stream keyword on the relevant message type.

← Back to the roadmap