What is gRPC?
-
Google Remote Procedure Calls open-source framework for building
APIs(similar to REST API).
It uses HTTP/2 for transport and Protocol Buffers (Protobuf) for efficient data serialization
Remote Procedure Call (RPC)
-
A communication model where a client requests a function to run on a
remote server as if it were local.
Protocol Buffers (Protobuf)
- Defines data structures and service interfaces in .proto files
.proto file (contract/interface definition)
-
.proto file is a IDL(Interface definition language) or schema definition
file. It defines:
1. Services: For example I want to create a service which responds with "hello" message.
2. Messages: What messages this service can send and recieve
REST API vs gRPC
REST gRPC
Speed slow faster
encoding text/json binary(via protobuf)
Transport HTTP/1.1 HTTP/2
gRPC hello Server
gRPC Server
-
Implement a gRPC server which responds with "hi", when it recieves
"hello".
Complete Project Structure:
project/
├── hello.proto
├── pb/
│ ├── hello.pb.go # Generated messages
│ └── hello_grpc.pb.go # Generated gRPC interfaces
├── server.go # Your server implementation
└── client.go # (Optional) Client example
Steps of creating server
-
1. hello.proto:
Define service and messages in this file
Suppose server recieves message HelloRequest and responds with message HelloResponse
// 1. Define service and messages in .proto file
$ cat hello.proto
syntax = "proto3";
package hello;
option go_package = "./pb";
service HelloService {
rpc hello(HelloRequest) returns (HelloResponse);
}
message HelloRequest {
string request = 1;
}
message HelloResponse {
string response = 1;
}
- 2. Generate .pb.go (ie protobuf code) using protoc compiler
// 2. Generate .pb.go (ie protobuf code) using protoc compiler
$ protoc --go_out=. --go-grpc_out=. hello.proto
$ ls pb
hello.pb.go hello_grpc.pb.go
-
3. hello.pb.go
For every message (from hello.proto file) 2 things are generated:
1. type struct
2. Methods for Serialization/Deserialization
$ cat hello.pb.go
// 1. type struct
type HelloRequest struct {
state protoimpl.MessageState `protogen:"open.v1"` // Internal state management
Request string `protobuf:"bytes,1,opt,name=request,proto3" json:"request,omitempty"` //Actual feild
unknownFields protoimpl.UnknownFields // For forward compatibility
sizeCache protoimpl.SizeCache // Performance optimization
}
// 2. Methods for Serialization/Deserialization
func (x *HelloRequest) Reset() {..} //Clears all fields to zero values
func (x *HelloRequest) String() string {..} //Implements Stringer interface for pretty printing
func (*HelloRequest) ProtoMessage() {..} // identifies this as a protobuf message
func (x *HelloRequest) ProtoReflect() protoreflect.Message {..} // enables reflection capabilities
func (*HelloRequest) Descriptor() ([]byte, []int) {..} // Legacy. v1 API compatibility
func (x *HelloRequest) GetRequest() string {..}
type HelloResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
Response string `protobuf:"bytes,1,opt,name=response,proto3" json:"response,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *HelloResponse) Reset() {..}
func (x *HelloResponse) String() string {..}
func (*HelloResponse) ProtoMessage() {}
func (x *HelloResponse) ProtoReflect() protoreflect.Message {..}
func (*HelloResponse) Descriptor() ([]byte, []int) {..}
func (x *HelloResponse) GetResponse() string {..}
-
4. server.go
1. Define a server struct that will implement generated interface.
2. Implement the exact method signature from the generated interface
Provide business logic inside the function
3. Create TCP listener
4. Create gRPC server
5. Register implementation with the gRPC server
6. Start listening
package main
import (
"context"
"log"
"net"
pb "your-project/pb" // Adjust import path
"google.golang.org/grpc"
)
// 1. Server struct that implements the generated interface
type server struct {
pb.UnimplementedHelloServiceServer // Embed for forward compatibility
}
// 2. Implement the exact method signature from the generated interface
func (s *server) Hello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloResponse, error) {
// Simple implementation - echo back with "Hello"
return &pb.HelloResponse{
Response: "Hello " + req.GetRequest(),
}, nil
}
func main() {
// 3. Create TCP listener
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
// 4. Create gRPC server
s := grpc.NewServer()
// 5. Register our implementation with the gRPC server
pb.RegisterHelloServiceServer(s, &server{})
log.Println("gRPC server listening on port 50051")
// 6. Start serving
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
gRPC Client
- 1. client.go
package main
import (
"context"
"log"
"time"
pb "your-project/pb"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
func main() {
// 1. Establish connection
conn, err := grpc.NewClient(
"localhost:50051",
grpc.WithTransportCredentials(insecure.NewCredentials()), // For local testing
grpc.WithBlock(), // Wait for connection
)
if err != nil {
log.Fatal(err)
}
defer conn.Close()
// 2. Create client from generated code
client := pb.NewHelloServiceClient(conn)
// 3. Prepare request using generated type
req := &pb.HelloRequest{
Request: "World",
}
// 4. Set timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// 5. Make RPC call
resp, err := client.Hello(ctx, req)
if err != nil {
log.Fatal(err)
}
// 6. Use response
log.Printf("Response: %s", resp.GetResponse())
}