Skip to content
Home
gRPC: A Complete Guide to Modern RPC

gRPC: A Complete Guide to Modern RPC

API Development API Development 8 min read 1655 words Beginner ExcellentWiki Editorial Team

gRPC is a high-performance, open-source RPC (Remote Procedure Call) framework developed by Google. It uses Protocol Buffers (protobuf) for serialization and HTTP/2 for transport, enabling features like bidirectional streaming, flow control, and multiplexing.

This guide covers the fundamentals of gRPC — defining services with protobuf, implementing servers and clients, streaming patterns, and when to choose gRPC over REST.

Protocol Buffers

Protocol Buffers are Google’s language-neutral, platform-neutral mechanism for serializing structured data. You define data structures in .proto files, and the protobuf compiler generates code in your chosen language.

Defining a Message

syntax = "proto3";

package user;

message User {
  int32 id = 1;
  string name = 2;
  string email = 3;
  repeated string roles = 4;
---

message GetUserRequest {
  int32 id = 1;
---

message ListUsersRequest {
  int32 page = 1;
  int32 limit = 2;
---

message ListUsersResponse {
  repeated User users = 1;
  int32 total = 2;
---

Each field has a unique number (1, 2, 3…) that identifies it in the binary encoding. These numbers should never change after deployment — changing a field number is a breaking change.

Scalar types include int32, int64, float, double, bool, string, and bytes. Complex types include enum, oneof (one of several fields can be set), map, and nested messages.

Defining a Service

service UserService {
  rpc GetUser(GetUserRequest) returns (User);
  rpc ListUsers(ListUsersRequest) returns (ListUsersResponse);
  rpc CreateUser(CreateUserRequest) returns (User);
  rpc UpdateUser(UpdateUserRequest) returns (User);
  rpc DeleteUser(DeleteUserRequest) returns (Empty);
---

gRPC supports four service methods:

  • Unary — Single request, single response (GetUser)
  • Server streaming — Single request, stream of responses
  • Client streaming — Stream of requests, single response
  • Bidirectional streaming — Stream of requests, stream of responses

Generating Code

Install the protobuf compiler (protoc) and the gRPC plugin for your language:

protoc --go_out=. --go-grpc_out=. user.proto

This generates:

  • user.pb.go — Message types and serialization code
  • user_grpc.pb.go — Client and server interfaces

Implementing a gRPC Server

Go Example

package main

import (
    "context"
    "net"
    "google.golang.org/grpc"
    pb "path/to/proto"
)

type userServer struct {
    pb.UnimplementedUserServiceServer
---

func (s *userServer) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.User, error) {
    user, err := db.FindUserByID(req.Id)
    if err != nil {
        return nil, err
    }
    return &pb.User{
        Id:    int32(user.ID),
        Name:  user.Name,
        Email: user.Email,
    }, nil
---

func main() {
    lis, _ := net.Listen("tcp", ":50051")
    s := grpc.NewServer()
    pb.RegisterUserServiceServer(s, &userServer{})
    s.Serve(lis)
---

Client Example

conn, _ := grpc.Dial("localhost:50051", grpc.WithInsecure())
defer conn.Close()

client := pb.NewUserServiceClient(conn)
user, _ := client.GetUser(context.Background(), &pb.GetUserRequest{Id: 42})
fmt.Println(user.Name)

Streaming

Streaming is one of gRPC’s most powerful features.

Server Streaming

The server sends multiple responses to a single request:

rpc SearchUsers(SearchRequest) returns (stream User);
func (s *userServer) SearchUsers(req *pb.SearchRequest, stream pb.UserService_SearchUsersServer) error {
    users, _ := db.SearchUsers(req.Query)
    for _, user := range users {
        stream.Send(&pb.User{Id: int32(user.ID), Name: user.Name})
    }
    return nil
---

Client Streaming

The client sends multiple requests and gets a single response:

rpc CreateUsers(stream CreateUserRequest) returns (CreateUsersResponse);

Bidirectional Streaming

Both sides stream simultaneously — useful for chat, real-time updates, and pipeline processing:

rpc Chat(stream ChatMessage) returns (stream ChatMessage);

Deadlines and Timeouts

gRPC clients should always set deadlines on RPC calls to prevent resource leaks from hanging requests. Unlike REST where HTTP timeouts are configured at the client level, gRPC supports per-call deadlines that propagate across service boundaries:

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

user, err := client.GetUser(ctx, &pb.GetUserRequest{Id: 42})

When a deadline is exceeded, the server receives a DeadlineExceeded status code and can cancel any ongoing work. This prevents cascading failures — if the database is slow, the gRPC call times out, the caller receives an error immediately, and the server stops processing the request instead of wasting resources on a response nobody will read. Set deadlines based on your SLOs: if your API targets p99 latency under 200ms, set client deadlines to 1-2 seconds to account for retries and network variance.

Interceptors

Interceptors are gRPC’s middleware — they run before or after RPC calls for cross-cutting concerns like logging, authentication, rate limiting, and tracing.

Server Interceptor

func loggingInterceptor(
    ctx context.Context,
    req interface{},
    info *grpc.UnaryServerInfo,
    handler grpc.UnaryHandler,
) (interface{}, error) {
    log.Printf("Method: %s, Request: %v", info.FullMethod, req)
    resp, err := handler(ctx, req)
    log.Printf("Response: %v, Error: %v", resp, err)
    return resp, err
---

s := grpc.NewServer(grpc.UnaryInterceptor(loggingInterceptor))

Client Interceptor

func authInterceptor(
    ctx context.Context,
    method string,
    req, reply interface{},
    cc *grpc.ClientConn,
    invoker grpc.UnaryInvoker,
    opts ...grpc.CallOption,
) error {
    ctx = metadata.AppendToOutgoingContext(ctx, "authorization", token)
    return invoker(ctx, method, req, reply, cc, opts...)
---

conn, _ := grpc.Dial("localhost:50051",
    grpc.WithUnaryInterceptor(authInterceptor))

gRPC vs REST

FeaturegRPCREST
TransportHTTP/2HTTP/1.1, HTTP/2
SerializationProtocol Buffers (binary)JSON, XML (text)
StreamingNative (unary, server, client, bidirectional)Limited (SSE, WebSocket)
Browser supportRequires gRPC-WebNative
CachingLimitedExcellent (HTTP caching)
Code generationBuilt-in (protoc)Third-party (OpenAPI generators)
Human readabilityBinary (not readable)JSON (readable)

When to Use gRPC

  • Microservices communication — High-performance, low-latency internal APIs
  • Streaming data — Real-time feeds, logs, event streams
  • Polyglot environments — Multiple languages need a common interface
  • Mobile apps — Smaller payloads conserve bandwidth

When to Use REST

  • Public APIs — Universal browser and tool support
  • Simple CRUD — Over-engineered for basic create/read/update/delete
  • Human-readable debugging — curl, browser dev tools
  • Heavy caching requirements — HTTP caching is simpler and more mature

Error Handling

gRPC defines a set of standard status codes for error handling. Common codes include NotFound, InvalidArgument, AlreadyExists, PermissionDenied, Unavailable, and Internal. Each error can include a message and detailed error details. The client unmarshals these into language-specific exception types.

Production Deployment

In production, use TLS for all gRPC connections. Configure keepalive pings to detect dead connections. Set max message size limits to prevent memory exhaustion. Enable reflection for debugging tools like grpcurl. Use health checking protocols for load balancer integration.

Health Checking Protocol

gRPC defines a standard health checking protocol defined in grpc.health.v1.Health. Implement the Check and Watch RPCs to report service health to load balancers and orchestration platforms:

import (
    "google.golang.org/grpc/health"
    healthpb "google.golang.org/grpc/health/grpc_health_v1"
)

healthServer := health.NewServer()
healthServer.SetServingStatus("user.UserService", healthpb.HealthCheckResponse_SERVING)
healthpb.RegisterHealthServer(s, healthServer)

Kubernetes liveness and readiness probes can use gRPC health checking directly (Kubernetes 1.24+), eliminating the need for HTTP health endpoints alongside gRPC services. Configure the health server to report NOT_SERVING when your service depends on a database that is unreachable, ensuring the load balancer routes traffic away from unhealthy instances. The Watch RPC enables streaming health status changes, useful for service mesh sidecars that need immediate awareness of connectivity changes.

gRPC Reflection

The gRPC Reflection API allows clients to discover available services, methods, and message types at runtime without requiring the proto file. Enable reflection in your server:

import "google.golang.org/grpc/reflection"

reflection.Register(s)

Tools like grpcurl and grpcui use reflection to provide interactive debugging, similar to how OpenAPI enables REST API exploration. Reflection is essential for development and debugging but should be disabled in production or protected behind authentication to prevent information disclosure about your service surface.

gRPC Load Balancing

gRPC supports several load balancing policies. Client-side load balancing uses the DNS resolver to discover backends and applies a balancing policy (round_robin, pick_first, weight_round_robin). For production deployments, use a dedicated gRPC proxy like Envoy or a service mesh (Linkerd, Istio) that provides advanced load balancing with least-request algorithms, circuit breaking, and outlier detection. gRPC’s long-lived HTTP/2 connections mean that simple round-robin DNS load balancing can cause uneven traffic distribution — a single connection may serve thousands of requests. Use subchannel-level load balancing (the round_robin service config) to distribute RPCs across multiple backend connections, or rely on an external proxy that terminates gRPC and load balances at the request level.

FAQ

How does gRPC compare to REST in terms of performance? gRPC is typically 5-10x faster than REST/JSON due to Protocol Buffers’ compact binary encoding and HTTP/2’s multiplexing. gRPC reduces payload size by 60-80% compared to JSON. For high-throughput microservices, gRPC’s performance advantage is substantial.

Can I use gRPC in the browser? Not directly — browsers do not support the HTTP/2 trailers that gRPC requires. Use gRPC-Web, which translates gRPC calls to HTTP/1.1 requests that browsers can handle. gRPC-Web requires a proxy (Envoy) or a gRPC-Web-compatible server implementation.

How do I handle gRPC message size limits? The default gRPC message size limit is 4 MB. Configure MaxCallRecvMsgSize and MaxCallSendMsgSize on both client and server for larger messages. For very large payloads, consider streaming the data in chunks or using a separate blob store.

What is the difference between gRPC streaming and WebSockets? gRPC streaming is built on HTTP/2 streams, which are lighter weight than WebSocket connections. gRPC supports both client and server streaming with automatic flow control. WebSockets are better for browser-based applications and have broader infrastructure support.

How do I version gRPC services? Follow protobuf best practices: never change field numbers, add new fields instead of modifying existing ones, use reserved fields for removed fields, and maintain backward compatibility. For breaking changes, create a new package version.

How do I set up gRPC with mutual TLS? Generate client and server certificates signed by your internal CA. Configure the server to require client certificates using credentials.NewTLS with tls.RequireAndVerifyClientCert. On the client side, load the client certificate and the server’s CA certificate when creating TLS credentials. For service mesh environments (Istio, Linkerd), mTLS is handled transparently by the sidecar proxy, requiring no application-level certificate configuration.

What monitoring tools work with gRPC? gRPC integrates with OpenTelemetry for distributed tracing and metrics collection. Prometheus exporters are available for gRPC client and server interceptors that automatically record request count, latency histograms, and error rates per method. Envoy proxy provides detailed gRPC telemetry when used as a sidecar or gateway. For debugging, grpcurl and grpcui offer REPL and web-based interfaces that use the reflection API for service discovery.

Conclusion

gRPC is an excellent choice for internal microservices, streaming applications, and performance-critical APIs. Protocol Buffers provide strong typing and efficient serialization, HTTP/2 enables multiplexing and streaming, and code generation ensures type safety across languages. For public web APIs, REST with JSON is still the standard — but gRPC is increasingly common alongside it.

For a comprehensive overview, read our article on Api Authentication Guide.

For a comprehensive overview, read our article on Api Caching Strategies.

Section: API Development 1655 words 8 min read Beginner 756 articles in section Report inaccuracy Back to top