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())
}