gRPC is an open-source Remote Procedure Call (RPC) framework developed by Google. It uses HTTP/2 as the transport layer and Protocol Buffers (protobuf) as the interface description language and serialisation format. Clients call methods on remote servers as if they were local function calls; the framework handles serialisation, transport, and error propagation.
gRPC is built on HTTP/2, which means it gets multiplexing, header compression, and bidirectional streaming for free. This makes it significantly more efficient than REST/JSON over HTTP/1.1 for high-throughput or latency-sensitive service communication.
How gRPC fits together
graph LR
proto[".proto file"]
gen["grpc_tools_ruby_protoc\n(code generator)"]
pb["greeter_pb.rb\n(message classes)"]
svc["greeter_services_pb.rb\n(stub + skeleton)"]
client["Ruby client\n(uses Stub)"]
server["Ruby server\n(implements Service)"]
http2["HTTP/2 transport"]
proto --> gen
gen --> pb
gen --> svc
pb --> client
pb --> server
svc --> client
svc --> server
client -->|"RPC call"| http2
http2 -->|"dispatched method"| server
The .proto file is the single source of truth. Both the client and server are generated from it, ensuring they share the same message shapes and method signatures at compile time.
Why gRPC over REST
| gRPC | REST/JSON | |
|---|---|---|
| Schema | Strongly typed .proto | Optional (OpenAPI) |
| Serialisation | Binary protobuf | Text JSON |
| Transport | HTTP/2 | HTTP/1.1 or HTTP/2 |
| Streaming | Native (4 modes) | Limited (SSE, WebSocket) |
| Code generation | Built-in | Third-party tools |
| Browser support | Requires gRPC-Web proxy | Native |
gRPC is a natural fit for internal service-to-service communication, microservices, and any scenario where you control both ends of the wire. REST remains the better choice for public-facing APIs consumed directly by browsers.
Protocol Buffers
Protocol Buffers (protobuf) is Google’s language-neutral, platform-neutral, extensible mechanism for serialising structured data. A .proto file defines:
- Messages structured data types equivalent to classes or structs.
- Services a named set of RPC methods, each with a request and response message.
- Fields typed, numbered members of a message; field numbers identify fields in the binary encoding.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
syntax = "proto3";
package greeter;
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}
Field numbers must never be reused once a message is in production. Changing a field number is a breaking change because the binary encoding uses numbers, not names, to identify fields.
Scalar types
| Proto type | Ruby type | Notes |
|---|---|---|
string | String | UTF-8 encoded |
int32 / int64 | Integer | Signed |
uint32 / uint64 | Integer | Unsigned |
bool | TrueClass / FalseClass | |
float / double | Float | |
bytes | String | Binary data |
Service types
gRPC supports four communication patterns, all defined in the .proto file:
| Type | Proto syntax | Description |
|---|---|---|
| Unary | rpc Method (Req) returns (Res) | One request, one response. Equivalent to a standard HTTP call. |
| Server streaming | rpc Method (Req) returns (stream Res) | One request; server sends a stream of responses. |
| Client streaming | rpc Method (stream Req) returns (Res) | Client sends a stream of requests; server replies once. |
| Bidirectional streaming | rpc Method (stream Req) returns (stream Res) | Both sides stream independently over a single connection. |
Ruby setup
Add the gems to your Gemfile:
1
2
gem 'grpc'
gem 'grpc-tools'
Install dependencies:
1
bundle install
The grpc gem provides the runtime (client stubs, server, channels, interceptors). The grpc-tools gem bundles the grpc_tools_ruby_protoc binary, which wraps protoc and the Ruby gRPC plugin so no separate protoc installation is required.
Generating Ruby code
Given greeter.proto in the project root, generate the Ruby files into lib/:
1
2
3
4
grpc_tools_ruby_protoc \
--ruby_out=lib \
--grpc_out=lib \
greeter.proto
This produces two files:
| File | Contents |
|---|---|
lib/greeter_pb.rb | Ruby classes for HelloRequest and HelloReply |
lib/greeter_services_pb.rb | Greeter::Service skeleton and Greeter::Stub client class |
Regenerate these files every time greeter.proto changes. Never edit the generated files by hand; any hand-edit is overwritten on the next generation.
Implementing the server
Subclass the generated Greeter::Service and implement each RPC method. The method receives the request object and a GRPC::ActiveCall::SingleReqView call object, and returns the response.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
require 'grpc'
require_relative '../lib/greeter_services_pb'
class GreeterServer < Greeter::Service
def say_hello(hello_req, _unused_call)
Greeter::HelloReply.new(message: "Hello, #{hello_req.name}!")
end
end
def main
server = GRPC::RpcServer.new
server.add_http2_port('0.0.0.0:50051', :this_port_is_insecure)
server.handle(GreeterServer)
puts 'gRPC server running on port 50051'
server.run_till_terminated_or_interrupted([1, 'int', 'SIGTERM'])
end
main
:this_port_is_insecure disables TLS for local development. In production, pass a GRPC::Core::ServerCredentials object with your certificate and key.
Implementing the client
Create a stub using the generated Greeter::Stub class, then call the RPC method directly.
1
2
3
4
5
6
7
8
9
10
11
12
13
require 'grpc'
require_relative '../lib/greeter_services_pb'
def main
stub = Greeter::Stub.new('localhost:50051', :this_channel_is_insecure)
request = Greeter::HelloRequest.new(name: 'Alice')
response = stub.say_hello(request)
puts "Server replied: #{response.message}"
end
main
Run the server in one terminal and the client in another:
1
ruby server.rb
1
2
ruby client.rb
# => Server replied: Hello, Alice!
Server streaming example
For a server-streaming RPC the client calls the method and iterates over the responses using each:
Add the streaming method to the proto:
1
2
3
4
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
rpc SayHelloManyTimes (HelloRequest) returns (stream HelloReply);
}
Server implementation:
1
2
3
4
5
def say_hello_many_times(hello_req, _unused_call)
5.times.lazy.map do |i|
Greeter::HelloReply.new(message: "Hello #{hello_req.name}, reply #{i + 1}")
end
end
Client consumption:
1
2
responses = stub.say_hello_many_times(Greeter::HelloRequest.new(name: 'Bob'))
responses.each { |reply| puts reply.message }
The server returns a lazy enumerator; the grpc runtime streams each element to the client as it is produced.
What to avoid
Do not edit generated files. greeter_pb.rb and greeter_services_pb.rb are overwritten every time you run grpc_tools_ruby_protoc. All business logic belongs in your own classes that inherit from or wrap the generated skeleton.
Do not use HTTP/1.1 load balancers in front of gRPC servers. gRPC requires HTTP/2, which uses a single persistent connection with multiplexed streams. HTTP/1.1 load balancers treat each stream as a connection and cannot balance traffic across multiple server instances. Use an HTTP/2-capable load balancer (AWS ALB with gRPC routing, Envoy, or a Kubernetes Gateway API implementation).
Do not skip deadlines on client calls. Without a deadline, a client call can block indefinitely if the server is slow or unresponsive. Set a deadline on every call:
1
stub.say_hello(request, deadline: Time.now + 5)
Do not reuse field numbers in .proto files. Once a field number has been used in a production message, removing or reassigning it breaks binary compatibility with older clients and servers still using that number. Mark removed fields with reserved instead.
Do not share a single stub across many goroutines or threads without understanding connection limits. The grpc Ruby gem manages a connection pool internally, but a single channel has a maximum number of concurrent streams (default 100 for HTTP/2). For high-concurrency workloads, benchmark your pool size and consider multiple channels or connection management at a higher level.