Getting started
Introduction
gRPC is a modern open source high-performance Remote Procedure Call (RPC) framework that uses Protocol Buffers as the interface definition language and runs over HTTP/2.
Installation
# Node.js
npm install @grpc/grpc-js @grpc/proto-loader
# Python
pip install grpcio grpcio-tools
# Go
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
Quick Example
syntax = "proto3";
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
Protocol Buffers Syntax (Proto3)
Basic Message
syntax = "proto3";
message Person {
string name = 1;
int32 id = 2;
string email = 3;
}
Field numbers 1-15 use 1 byte encoding. Use them for frequent fields.
Scalar Types
| Proto Type | Description | Example |
|---|---|---|
int32 |
Signed 32-bit integer | -2147483648 to 2147483647 |
int64 |
Signed 64-bit integer | Large numbers |
uint32 |
Unsigned 32-bit | 0 to 4294967295 |
uint64 |
Unsigned 64-bit | Large positive numbers |
string |
UTF-8 text | "hello" |
bool |
Boolean | true or false |
bytes |
Binary data | b"binary" |
float |
32-bit float | 3.14 |
double |
64-bit float | 3.14159 |
Enums
enum Status {
UNKNOWN = 0; // First value must be 0
ACTIVE = 1;
INACTIVE = 2;
DELETED = 3;
}
message User {
string name = 1;
Status status = 2;
}
Repeated Fields
message User {
string name = 1;
repeated string emails = 2; // Array
repeated int32 scores = 3;
}
Nested Messages
message Person {
string name = 1;
message PhoneNumber {
string number = 1;
string type = 2;
}
repeated PhoneNumber phones = 2;
}
Reserved Fields
message Foo {
reserved 2, 15, 9 to 11;
reserved "foo", "bar";
string name = 1;
// int32 deleted = 2; // Reserved!
}
Prevent field number/name reuse to avoid compatibility issues.
Maps
message Project {
string name = 1;
map<string, string> metadata = 2;
map<int32, User> users = 3;
}
Oneof
message SearchRequest {
string query = 1;
oneof filter {
string category = 2;
int32 user_id = 3;
bool favorites = 4;
}
}
Only one field can be set at a time.
Service Definition (4 RPC Types)
Unary RPC
service UserService {
rpc GetUser (GetUserRequest) returns (User) {}
}
message GetUserRequest {
int32 id = 1;
}
Single request, single response.
Server Streaming
service DataService {
rpc StreamData (DataRequest)
returns (stream DataChunk) {}
}
message DataRequest {
string query = 1;
}
message DataChunk {
bytes data = 1;
}
Single request, multiple responses.
Client Streaming
service UploadService {
rpc UploadFile (stream FileChunk)
returns (UploadResponse) {}
}
message FileChunk {
bytes data = 1;
}
Multiple requests, single response.
Bidirectional Streaming
service ChatService {
rpc Chat (stream ChatMessage)
returns (stream ChatMessage) {}
}
message ChatMessage {
string user = 1;
string text = 2;
}
Multiple requests and responses.
Code Generation
Node.js
# Install tools
npm install -g grpc-tools
# Generate code
grpc_tools_node_protoc \
--js_out=import_style=commonjs,binary:./generated \
--grpc_out=grpc_js:./generated \
--plugin=protoc-gen-grpc=`which grpc_tools_node_protoc_plugin` \
proto/service.proto
Python
# Generate code
python -m grpc_tools.protoc \
-I./proto \
--python_out=./generated \
--grpc_python_out=./generated \
proto/service.proto
Go
# Generate code
protoc \
--go_out=./generated \
--go_opt=paths=source_relative \
--go-grpc_out=./generated \
--go-grpc_opt=paths=source_relative \
proto/service.proto
Server Examples
Node.js Server
const grpc = require("@grpc/grpc-js");
const protoLoader = require("@grpc/proto-loader");
const packageDefinition = protoLoader.loadSync("service.proto", {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true,
});
const proto = grpc.loadPackageDefinition(packageDefinition);
function sayHello(call, callback) {
callback(null, { message: "Hello " + call.request.name });
}
const server = new grpc.Server();
server.addService(proto.Greeter.service, { sayHello: sayHello });
server.bindAsync(
"0.0.0.0:50051",
grpc.ServerCredentials.createInsecure(),
() => {
server.start();
console.log("Server running at http://0.0.0.0:50051");
},
);
Python Server
import grpc
from concurrent import futures
import service_pb2
import service_pb2_grpc
class GreeterServicer(service_pb2_grpc.GreeterServicer):
def SayHello(self, request, context):
return service_pb2.HelloReply(message=f'Hello {request.name}')
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
service_pb2_grpc.add_GreeterServicer_to_server(GreeterServicer(), server)
server.add_insecure_port('[::]:50051')
server.start()
server.wait_for_termination()
Go Server
package main
import (
"context"
"log"
"net"
"google.golang.org/grpc"
pb "path/to/generated"
)
type server struct {
pb.UnimplementedGreeterServer
}
func (s *server) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloReply, error) {
return &pb.HelloReply{Message: "Hello " + req.Name}, nil
}
func main() {
lis, _ := net.Listen("tcp", ":50051")
s := grpc.NewServer()
pb.RegisterGreeterServer(s, &server{})
log.Fatal(s.Serve(lis))
}
Client Examples
Node.js Client
const grpc = require("@grpc/grpc-js");
const protoLoader = require("@grpc/proto-loader");
const packageDefinition = protoLoader.loadSync("service.proto");
const proto = grpc.loadPackageDefinition(packageDefinition);
const client = new proto.Greeter(
"localhost:50051",
grpc.credentials.createInsecure(),
);
client.sayHello({ name: "World" }, (error, response) => {
if (error) {
console.error(error);
} else {
console.log("Greeting:", response.message);
}
});
Python Client
import grpc
import service_pb2
import service_pb2_grpc
channel = grpc.insecure_channel('localhost:50051')
stub = service_pb2_grpc.GreeterStub(channel)
response = stub.SayHello(service_pb2.HelloRequest(name='World'))
print(f'Greeting: {response.message}')
Go Client
package main
import (
"context"
"log"
"google.golang.org/grpc"
pb "path/to/generated"
)
func main() {
conn, _ := grpc.Dial("localhost:50051", grpc.WithInsecure())
defer conn.Close()
client := pb.NewGreeterClient(conn)
response, err := client.SayHello(context.Background(),
&pb.HelloRequest{Name: "World"})
if err != nil {
log.Fatal(err)
}
log.Printf("Greeting: %s", response.Message)
}
Streaming Examples
Server Streaming (Go)
func (s *server) StreamData(req *pb.DataRequest, stream pb.DataService_StreamDataServer) error {
for i := 0; i < 10; i++ {
chunk := &pb.DataChunk{Data: []byte(fmt.Sprintf("Chunk %d", i))}
if err := stream.Send(chunk); err != nil {
return err
}
time.Sleep(time.Second)
}
return nil
}
// Client
stream, _ := client.StreamData(context.Background(), &pb.DataRequest{Query: "test"})
for {
chunk, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
log.Fatal(err)
}
log.Printf("Received: %s", chunk.Data)
}
Bidirectional Streaming (Python)
# Server
def Chat(self, request_iterator, context):
for message in request_iterator:
response = service_pb2.ChatMessage(
user='Bot',
text=f'Echo: {message.text}'
)
yield response
# Client
def generate_messages():
messages = ['Hello', 'How are you?', 'Goodbye']
for msg in messages:
yield service_pb2.ChatMessage(user='User', text=msg)
responses = stub.Chat(generate_messages())
for response in responses:
print(f'{response.user}: {response.text}')
Status Codes
Common Codes
| Code | Number | Description |
|---|---|---|
OK |
0 | Success |
CANCELLED |
1 | Operation cancelled |
UNKNOWN |
2 | Unknown error |
INVALID_ARGUMENT |
3 | Invalid argument |
DEADLINE_EXCEEDED |
4 | Timeout |
NOT_FOUND |
5 | Resource not found |
ALREADY_EXISTS |
6 | Resource exists |
PERMISSION_DENIED |
7 | No permission |
Error Codes
| Code | Number | Description |
|---|---|---|
RESOURCE_EXHAUSTED |
8 | Out of resources |
FAILED_PRECONDITION |
9 | Precondition failed |
ABORTED |
10 | Operation aborted |
OUT_OF_RANGE |
11 | Out of range |
UNIMPLEMENTED |
12 | Not implemented |
INTERNAL |
13 | Internal error |
UNAVAILABLE |
14 | Service unavailable |
DATA_LOSS |
15 | Data loss |
UNAUTHENTICATED |
16 | Not authenticated |
Error Handling
# Server
context.set_code(grpc.StatusCode.NOT_FOUND)
context.set_details('User not found')
return service_pb2.User()
# Client
try:
response = stub.GetUser(request)
except grpc.RpcError as e:
print(f'Error: {e.code()}: {e.details()}')
Authentication
SSL/TLS
# Server
server_credentials = grpc.ssl_server_credentials(
[(private_key, certificate_chain)]
)
server.add_secure_port('[::]:50051', server_credentials)
# Client
credentials = grpc.ssl_channel_credentials(
root_certificates=root_cert
)
channel = grpc.secure_channel('localhost:50051', credentials)
Token-based (Metadata)
# Client
def auth_interceptor(context, callback):
metadata = (('authorization', 'Bearer YOUR_TOKEN'),)
callback(metadata, None)
channel = grpc.insecure_channel('localhost:50051')
channel = grpc.intercept_channel(channel, auth_interceptor)
# Server
def validate_token(context):
metadata = dict(context.invocation_metadata())
token = metadata.get('authorization', '')
if not token.startswith('Bearer '):
context.abort(grpc.StatusCode.UNAUTHENTICATED, 'Invalid token')
Google Default Credentials
import "google.golang.org/grpc/credentials/oauth"
perRPC, _ := oauth.NewApplicationDefault(context.Background(), scope)
conn, _ := grpc.Dial(
serverAddr,
grpc.WithTransportCredentials(credentials.NewClientTLSFromCert(nil, "")),
grpc.WithPerRPCCredentials(perRPC),
)
Deadlines & Timeouts
Setting Deadlines
// Client - set 5 second deadline
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
response, err := client.SayHello(ctx, &pb.HelloRequest{Name: "World"})
if err != nil {
if status.Code(err) == codes.DeadlineExceeded {
log.Println("Request timed out")
}
}
# Python - set 5 second timeout
try:
response = stub.SayHello(
request,
timeout=5.0
)
except grpc.RpcError as e:
if e.code() == grpc.StatusCode.DEADLINE_EXCEEDED:
print("Request timed out")
Propagating Deadlines
// Server - propagate deadline to downstream service
func (s *server) ProcessRequest(ctx context.Context, req *pb.Request) (*pb.Response, error) {
// Deadline from incoming context is automatically propagated
downstreamResponse, err := downstreamClient.DoWork(ctx, &pb.Work{})
if err != nil {
return nil, err
}
return &pb.Response{}, nil
}
Metadata
Sending Metadata
// Client - send metadata
md := metadata.Pairs(
"timestamp", time.Now().Format(time.StampNano),
"client-id", "web-client",
)
ctx := metadata.NewOutgoingContext(context.Background(), md)
response, err := client.SayHello(ctx, &pb.HelloRequest{Name: "World"})
# Python - send metadata
metadata = (
('timestamp', str(time.time())),
('client-id', 'web-client'),
)
response = stub.SayHello(request, metadata=metadata)
Reading Metadata
// Server - read metadata
func (s *server) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloReply, error) {
md, ok := metadata.FromIncomingContext(ctx)
if ok {
clientID := md.Get("client-id")
log.Printf("Client ID: %v", clientID)
}
return &pb.HelloReply{Message: "Hello"}, nil
}
Binary Metadata
// Keys ending in "-bin" are treated as binary
md := metadata.Pairs(
"my-key", "text value",
"my-binary-key-bin", string([]byte{1, 2, 3}),
)
Binary keys must end with -bin suffix.
Interceptors
Unary Interceptor (Go)
func unaryInterceptor(
ctx context.Context,
req interface{},
info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler,
) (interface{}, error) {
// Pre-processing
log.Printf("Method: %s", info.FullMethod)
start := time.Now()
// Call handler
resp, err := handler(ctx, req)
// Post-processing
log.Printf("Duration: %v", time.Since(start))
return resp, err
}
// Register
server := grpc.NewServer(
grpc.UnaryInterceptor(unaryInterceptor),
)
Stream Interceptor (Go)
func streamInterceptor(
srv interface{},
ss grpc.ServerStream,
info *grpc.StreamServerInfo,
handler grpc.StreamHandler,
) error {
log.Printf("Stream Method: %s", info.FullMethod)
return handler(srv, ss)
}
// Register
server := grpc.NewServer(
grpc.StreamInterceptor(streamInterceptor),
)
Load Balancing
Client-side Load Balancing
import "google.golang.org/grpc/balancer/roundrobin"
// Round-robin
conn, err := grpc.Dial(
"dns:///my-service:50051",
grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy":"round_robin"}`),
grpc.WithInsecure(),
)
Name Resolver
// Custom resolver for service discovery
conn, err := grpc.Dial(
"my-custom-scheme://authority/service",
grpc.WithInsecure(),
)
Health Checking
Server Health Check
import "google.golang.org/grpc/health"
import "google.golang.org/grpc/health/grpc_health_v1"
healthServer := health.NewServer()
grpc_health_v1.RegisterHealthServer(s, healthServer)
healthServer.SetServingStatus("myservice", grpc_health_v1.HealthCheckResponse_SERVING)
Client Health Check
import "google.golang.org/grpc/health/grpc_health_v1"
healthClient := grpc_health_v1.NewHealthClient(conn)
resp, err := healthClient.Check(
context.Background(),
&grpc_health_v1.HealthCheckRequest{Service: "myservice"},
)
if resp.Status != grpc_health_v1.HealthCheckResponse_SERVING {
log.Println("Service unhealthy")
}
Best Practices
Field Numbers
message User {
// Use 1-15 for frequent fields (1 byte encoding)
string id = 1;
string name = 2;
string email = 3;
// Use 16+ for less frequent fields (2 bytes)
string bio = 16;
string avatar_url = 17;
}
Always Set Deadlines
// ✅ Good - explicit timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
response, err := client.GetUser(ctx, request)
// ❌ Bad - no timeout (may hang forever)
response, err := client.GetUser(context.Background(), request)
Reserve Deleted Fields
message User {
reserved 2, 15; // Never reuse these numbers
reserved "old_field", "deprecated_field";
string name = 1;
// string deleted_field = 2; // RESERVED!
}
Check EOF on Streams
// ✅ Correct stream reading
for {
msg, err := stream.Recv()
if err == io.EOF {
break // Stream ended normally
}
if err != nil {
return err // Actual error
}
// Process msg
}
Use SSL/TLS
// ✅ Production - use TLS
creds, _ := credentials.NewClientTLSFromFile("server.crt", "")
conn, _ := grpc.Dial("server:443", grpc.WithTransportCredentials(creds))
// ❌ Development only - insecure
conn, _ := grpc.Dial("server:50051", grpc.WithInsecure())
Metadata Reserved Keys
// ✅ Good - custom metadata
md := metadata.Pairs("x-request-id", "12345")
// ❌ Bad - grpc- prefix is reserved
md := metadata.Pairs("grpc-custom", "value")
Gotchas
Deadlines Have No Default
gRPC does not set a default deadline. Calls without a deadline can hang indefinitely. Always set explicit timeouts on client calls.
Field Numbers are Permanent
Once a field number is used in production, it cannot be reused. Use reserved to prevent accidental reuse when removing fields.
Enum First Value Must Be 0
// ✅ Correct
enum Status {
UNKNOWN = 0;
ACTIVE = 1;
}
// ❌ Error: first enum value must be zero
enum Status {
ACTIVE = 1;
INACTIVE = 2;
}
Metadata Keys Are Lowercase
gRPC automatically lowercases metadata keys. Case-sensitive matching will fail.
Streaming Context Cancellation
When client cancels context, server stream handler should check ctx.Done() to avoid processing cancelled requests.
Supported Languages
- C++
- C#
- Dart
- Go
- Java
- Kotlin
- Node.js
- Objective-C
- PHP
- Python
- Ruby
- Swift
Also see
- gRPC Official Documentation - Official guides and tutorials
- Protocol Buffers Documentation - Proto3 language guide
- gRPC Status Codes - Complete status code reference
- gRPC Best Practices - Performance and optimization guide
- Awesome gRPC - Community resources and tools