DEV Community

Achraf Ben Hamou
Achraf Ben Hamou

Posted on

RPC in Python: gRPC, Protocol Buffers, and REST vs GraphQL

When building backend systems, choosing the right communication protocol can have a significant impact on performance, maintainability, and scalability.

REST is still the most common choice for public APIs, while GraphQL has become popular when clients need flexible data fetching.

But when services need to communicate efficiently with each other, another approach becomes particularly interesting: RPC, and more specifically gRPC.

In this article, we'll explore:

  • What RPC actually means
  • How gRPC works
  • What Protocol Buffers are
  • How to build a gRPC service with Python
  • gRPC vs REST vs GraphQL
  • When you should use each approach

1. What is RPC?

RPC stands for Remote Procedure Call.

The idea is simple: instead of thinking in terms of HTTP resources, we think in terms of calling a method on a remote service.

For example, with a traditional REST API, we might have:

GET /users/42
Enter fullscreen mode Exit fullscreen mode

With RPC, the mental model is closer to:

UserService.GetUser(42)
Enter fullscreen mode Exit fullscreen mode

The client calls a method, and the server executes that method remotely.

Of course, the method isn't actually local. The RPC framework takes care of the communication between the client and the server.

gRPC follows this model and allows a client application to call methods on another machine almost like calling local methods.


2. What is gRPC?

gRPC is an RPC framework originally developed at Google.

It allows services to communicate using strongly defined service contracts.

A typical gRPC architecture looks like this:

┌─────────────┐
│   Client    │
│   Python    │
└──────┬──────┘
       │
       │ gRPC
       │
       ▼
┌─────────────────┐
│      Server     │
│     Python      │
└─────────────────┘
Enter fullscreen mode Exit fullscreen mode

Instead of manually designing HTTP endpoints and JSON payloads, we define the service contract in a .proto file.

gRPC can use Protocol Buffers as both the interface definition language and the message format.


3. Protocol Buffers: the contract

Protocol Buffers, commonly called Protobuf, are used to define the messages and services exchanged between applications.

For example:

syntax = "proto3";

service UserService {
    rpc GetUser(GetUserRequest) returns (User);
}

message GetUserRequest {
    int32 id = 1;
}

message User {
    int32 id = 1;
    string name = 2;
    string email = 3;
}
Enter fullscreen mode Exit fullscreen mode

This file describes our API contract.

We have:

  • A UserService
  • A GetUser RPC method
  • A GetUserRequest
  • A User response

The important part is that the contract is language-independent.

A Python service could communicate with a Go, Java, C#, or Node.js service using the same .proto definition.

gRPC officially supports multiple languages, including Python, Go, Java, C#, Node.js, Ruby, PHP and others.


4. Building a gRPC API with Python

Let's build a very small service.

Install the dependencies

python -m pip install grpcio grpcio-tools
Enter fullscreen mode Exit fullscreen mode

The official Python gRPC documentation uses grpcio for the runtime and grpcio-tools for generating Python code from .proto definitions.

Our project could look like this:

grpc-demo/
│
├── proto/
│   └── user.proto
│
├── server.py
├── client.py
└── requirements.txt
Enter fullscreen mode Exit fullscreen mode

5. Define the service

Create:

proto/user.proto
Enter fullscreen mode Exit fullscreen mode

with:

syntax = "proto3";

package user;

service UserService {
    rpc GetUser(GetUserRequest) returns (User);
}

message GetUserRequest {
    int32 id = 1;
}

message User {
    int32 id = 1;
    string name = 2;
    string email = 3;
}
Enter fullscreen mode Exit fullscreen mode

We have now defined our API contract.


6. Generate Python code

We can generate the Python client/server code with:

python -m grpc_tools.protoc \
    -I./proto \
    --python_out=. \
    --grpc_python_out=. \
    ./proto/user.proto
Enter fullscreen mode Exit fullscreen mode

This generates files similar to:

user_pb2.py
user_pb2_grpc.py
Enter fullscreen mode Exit fullscreen mode

The first contains the generated Protocol Buffer message classes, while the second contains the gRPC-specific client and server code.

You normally don't edit these generated files manually.


7. Implement the Python server

Now we can implement the service.

from concurrent import futures

import grpc

import user_pb2
import user_pb2_grpc


class UserService(user_pb2_grpc.UserServiceServicer):

    def GetUser(self, request, context):
        return user_pb2.User(
            id=request.id,
            name="John Doe",
            email="john@example.com",
        )


def serve():
    server = grpc.server(
        futures.ThreadPoolExecutor(max_workers=10)
    )

    user_pb2_grpc.add_UserServiceServicer_to_server(
        UserService(),
        server,
    )

    server.add_insecure_port("[::]:50051")

    server.start()

    print("gRPC server running on port 50051")

    server.wait_for_termination()


if __name__ == "__main__":
    serve()
Enter fullscreen mode Exit fullscreen mode

The generated UserServiceServicer provides the interface that our Python implementation extends.

The official gRPC Python examples follow the same general pattern: create a server, register the generated servicer, bind a port, and start the server.


8. Create the Python client

Now let's consume the service.

import grpc

import user_pb2
import user_pb2_grpc


def main():
    with grpc.insecure_channel("localhost:50051") as channel:

        client = user_pb2_grpc.UserServiceStub(channel)

        response = client.GetUser(
            user_pb2.GetUserRequest(id=42)
        )

        print(response)


if __name__ == "__main__":
    main()
Enter fullscreen mode Exit fullscreen mode

The interesting part is:

client.GetUser(...)
Enter fullscreen mode Exit fullscreen mode

From the developer's perspective, it looks very similar to calling a normal Python method.

Behind the scenes, however, the request is sent to another process or machine.

The gRPC Python API generates a client stub from the .proto service definition.


9. gRPC supports streaming

One of the features that makes gRPC particularly interesting for backend systems is streaming.

There are four RPC patterns:

Unary RPC

One request → one response.

Client ── Request ──> Server
Client <── Response ─ Server
Enter fullscreen mode Exit fullscreen mode

Example:

rpc GetUser(GetUserRequest) returns (User);
Enter fullscreen mode Exit fullscreen mode

Server streaming

One request → multiple responses.

rpc ListUsers(ListUsersRequest) returns (stream User);
Enter fullscreen mode Exit fullscreen mode

Useful when a server needs to continuously send data to a client.

Client streaming

Multiple requests → one response.

rpc UploadUsers(stream User) returns (UploadSummary);
Enter fullscreen mode Exit fullscreen mode

Useful for sending a stream of data to the server.

Bidirectional streaming

Multiple requests ↔ multiple responses.

rpc Chat(stream Message) returns (stream Message);
Enter fullscreen mode Exit fullscreen mode

Both sides can continuously exchange messages.

gRPC's Python documentation demonstrates all four communication patterns.


10. gRPC vs REST

So when should we use gRPC instead of REST?

Let's compare them.

gRPC REST
Protocol HTTP/2 Usually HTTP/1.1 or HTTP/2
Data format Protobuf Usually JSON
Contract .proto OpenAPI often used
Code generation Built-in Optional
Streaming Excellent More limited
Browser support More complex Excellent
Human-readable payload No Yes
Microservices Excellent Excellent
Public APIs Less common Excellent

REST is extremely convenient for public APIs.

For example:

GET /users/42
Enter fullscreen mode Exit fullscreen mode

is easy to understand and easy to consume using:

curl
Enter fullscreen mode Exit fullscreen mode

A JSON response is also easy to inspect:

{
    "id": 42,
    "name": "John Doe",
    "email": "john@example.com"
}
Enter fullscreen mode Exit fullscreen mode

This simplicity is one of REST's biggest advantages.


11. gRPC vs GraphQL

GraphQL solves a different problem.

Instead of exposing multiple REST endpoints, GraphQL usually exposes a schema through which clients can request exactly the fields they need.

For example:

query {
    user(id: 42) {
        name
        email
    }
}
Enter fullscreen mode Exit fullscreen mode

This is extremely useful when different clients have different data requirements.

For example:

Mobile App
     │
     ▼
 GraphQL
     │
 ┌───┼─────────┐
 ▼   ▼         ▼
User Order   Product
Service      Service
Enter fullscreen mode Exit fullscreen mode

GraphQL is particularly attractive for frontend-facing APIs.

gRPC, on the other hand, is often a better fit when backend services need a strongly typed and efficient service-to-service contract.


12. Which one should you choose?

There isn't a universal winner.

Choose REST when:

  • You are building a public API
  • Browser compatibility matters
  • Simplicity is important
  • Human-readable JSON is useful
  • You want a familiar HTTP interface

Choose GraphQL when:

  • Clients need different subsets of data
  • You have complex frontend requirements
  • You want clients to control the shape of responses
  • You are building a frontend-facing aggregation layer

Choose gRPC when:

  • You are building microservices
  • You control both client and server
  • Strong contracts are important
  • Performance matters
  • You need streaming
  • You have multiple programming languages in your architecture

A common production architecture can even combine them.

For example:

                 Internet
                    │
                    ▼
              ┌───────────┐
              │ REST/API  │
              │ Gateway   │
              └─────┬─────┘
                    │
              ┌─────▼─────┐
              │ User      │
              │ Service   │
              └─────┬─────┘
                    │ gRPC
          ┌─────────┼──────────┐
          ▼         ▼          ▼
       Orders    Payments   Products
       Service    Service    Service
Enter fullscreen mode Exit fullscreen mode

REST can be the external interface while gRPC handles internal service-to-service communication.


13. The biggest advantage: a contract-first approach

For me, one of the strongest advantages of gRPC isn't simply performance.

It's the contract.

With:

service UserService {
    rpc GetUser(GetUserRequest) returns (User);
}
Enter fullscreen mode Exit fullscreen mode

the API contract is explicit.

From the same definition, tooling can generate client/server code.

This reduces a class of problems that can happen when API contracts are maintained manually.

Protocol Buffers also provide generated Python classes rather than requiring developers to manually serialize and deserialize every message.


14. But gRPC isn't always the right choice

There are trade-offs.

gRPC can be more complex than a simple REST API.

For example, debugging this:

Client
  │
  ▼
gRPC
  │
HTTP/2
  │
Protobuf
  │
Server
Enter fullscreen mode Exit fullscreen mode

can be less convenient than inspecting a JSON REST request.

Also, browser clients typically require additional considerations such as gRPC-Web rather than directly using the standard gRPC protocol.

So choosing gRPC simply because it is "faster" is not enough.

The architecture and communication requirements should drive the decision.


15. Final thoughts

As a Python Backend Developer, understanding gRPC is valuable because modern distributed systems often involve multiple services communicating with each other.

REST remains an excellent choice for many APIs.

GraphQL is powerful when clients need flexible access to data.

gRPC becomes particularly interesting for internal service-to-service communication where strong contracts, generated code, and streaming are important.

The key lesson is:

Don't choose a protocol because it is popular. Choose it because it fits the communication problem you're solving.

For a Python backend engineer, knowing REST + GraphQL + gRPC gives you a much broader understanding of API architecture than knowing only how to build CRUD endpoints.


Resources


About the author

I'm a Python Backend Developer interested in distributed systems, APIs, microservices, databases, and backend architecture.

I'm documenting what I learn while building backend systems and exploring technologies such as Python, FastAPI, PostgreSQL, Docker, gRPC, and cloud infrastructure.

Top comments (0)