What Is gRPC?
Remote Procedure Call
Section titled “Remote Procedure Call”gRPC stands for gRPC Remote Procedure Call (yes, it’s recursive). The important half is RPC: the idea that calling code on another machine should look like calling a normal function.
Without RPC, a service-to-service call means: build a URL, choose a verb, serialize a body to JSON, send an HTTP request, check the status code, parse the response, and hope both sides agreed on the shape. RPC collapses all of that into one line:
user = userService.GetUser(id: 42)You call a method. You get a typed result. The transport, serialization, and error handling are generated for you and hidden behind the call.
The contract comes first
Section titled “The contract comes first”The thing that makes this possible is a contract written before any implementation — a .proto file. It declares the messages (the data) and the service (the methods):
syntax = "proto3";package user.v1;
message GetUserRequest { int64 id = 1;}
message User { int64 id = 1; string name = 2; string email = 3;}
service UserService { rpc GetUser(GetUserRequest) returns (User);}This file is the single source of truth. It is language-neutral: the same .proto produces a Go server, a Python client, and a Node service that all interoperate perfectly, because they were all generated from the same declaration.
Generate, then call
Section titled “Generate, then call”The workflow has three steps, and only the middle one is code you write:
flowchart LR proto[".proto contract"] -->|protoc| stubs["generated stubs (client + server)"] stubs --> impl["you implement the server methods"] impl --> run["client calls stub.GetUser(42)"] run -->|HTTP/2 + protobuf| server["server runs GetUser, returns User"] server --> run
- Write the
.protoand runprotoc(the protobuf compiler) to generate typed stubs. - Implement the server — fill in the body of
GetUser. - Call from the client — invoke the generated stub method; the request is serialized to protobuf, sent over HTTP/2, and the typed response comes back.
Here is the client call in three languages. Notice how little there is: no URL, no JSON, no status-code checking — just a method call.
conn, _ := grpc.NewClient("localhost:50051", grpc.WithTransportCredentials(insecure.NewCredentials()))defer conn.Close()client := userv1.NewUserServiceClient(conn)
user, err := client.GetUser(ctx, &userv1.GetUserRequest{Id: 42})if err != nil { log.Fatal(err)}fmt.Println(user.Name) // typed field, no JSON parsingchannel = grpc.insecure_channel("localhost:50051")client = user_pb2_grpc.UserServiceStub(channel)
user = client.GetUser(user_pb2.GetUserRequest(id=42))print(user.name) # typed field, no JSON parsingconst client = new UserServiceClient( 'localhost:50051', credentials.createInsecure(),);
client.getUser({ id: 42 }, (err, user) => { if (err) throw err; console.log(user.name); // typed field, no JSON parsing});What gRPC gives you for free
Section titled “What gRPC gives you for free”Because everything flows from the contract, the framework can hand you a lot:
- Type safety across the wire — the client and server can’t disagree about field names or types; they’re generated from the same schema.
- Efficient binary encoding — protobuf is smaller and faster to parse than JSON.
- Streaming — the same model extends to sending many messages in either direction (a whole later module).
- Cross-language interop — 11+ official languages, all speaking the same protocol.
- Built-in features — deadlines, cancellation, metadata, interceptors, and load balancing come with the framework, not as things you reinvent.