gRPC vs REST: Side by Side Comparison
Communication protocols play important role in modern software applications. They define standards for how two applications can communicate with each other through requests and responses. Two of the most common and widely used protocols today are REST and gRPC.
REST has been around for decades and is a staple of web APIs, backend and other internet-based applications. GRPC is a newer protocol developed by Google that offers performance benefits through an RPC-style architecture. Both have their pros and cons depending on the use case.
In this post, we’ll provide an overview of REST vs GRPC, dive into their key differences, pros and cons of each one.
What is REST?
REST (Representational State Transfer) is an architectural style for building distributed systems and APIs. It was first introduced by computer scientist Roy Fielding in his 2000 doctoral dissertation.
Some key principles of REST include:
- Client-server - The API has a client-server separation. The client handles the user interface concerns while the server handles the data storage and business logic.
- Stateless - No client information is stored on the server between requests. Each request has all the necessary information to complete it.
- Cacheable - API responses should be marked cacheable or not to improve performance.
- Uniform interface - Resources are identified through URIs. Standard HTTP verbs like GET, POST, PUT, DELETE operate on them.
- Layered system - Client cannot tell if connected directly to end server or to intermediary along the way.
Some common use cases of REST APIs include:
- Public HTTP APIs accessible over the internet
- Web Services supporting web/mobile applications
- Interoperability and integration between different systems
REST relies on text-based formats like JSON and XML for request and response payloads. It is simple, flexible, and provides a loose coupling between client/server. The stateless nature also allows REST systems to scale well horizontally.
REST Golang Example
Let's illustrate this communication protocol with an example in Golang:
type User struct {
ID int `json:"id"`
Name string `json:"name"`
}
var users = []User{
{ID: 1, Name: "Alice"},
{ID: 2, Name: "Bob"},
}
func getUsers(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(users)
}
func main() {
http.HandleFunc("/users", getUsers)
fmt.Println("Server started on :8080")
http.ListenAndServe(":8080", nil)
}
This implements a simple REST API that serves the user data as JSON over HTTP. The User struct defines the data schema, getUsers handles the HTTP request, and main sets up the server.
Now let's make request to this server using a different language
import requests
response = requests.get("http://localhost:8080/users")
if response.status_code == 200:
users = response.json()
for user in users:
print(f"ID: {user['id']}, Name: {user['name']}")
else:
print(f"Error: {response.status_code}")
This code makes an HTTP GET request to the /users
endpoint. It uses the requests module to make the API call and get the response. If the response status code is 200 OK, it parses the JSON body into a dict. If the API call fails with a non-200 status, it simply prints an error with the status code.
What is gRPC?
gRPC is an open source remote procedure call (RPC) framework initially developed by Google. It uses HTTP/2 as the transport protocol and Protocol Buffers as the messaging format.
Some key aspects of gRPC:
- Protocol Buffers - Google's language-neutral, platform-neutral extensible mechanism for serializing structured data. The proto files get compiled into strongly-typed language bindings for the gRPC client and server.
- Contract-first API - The gRPC service contract is defined in a .proto file which declares the RPC endpoints and message structures. Client and server code is generated from this .proto.
- Strongly typed - Protocol Buffers are statically typed so the RPC payloads have validation checks.
- Binary payload - Protocol Buffers serialize to a compact binary format compared to text-based JSON/XML.
- Bi-directional streaming - gRPC supports all combinations of uni-directional, bidirectional, and request-response streaming.
gRPC was open sourced by Google in 2015 and is now very commonly used for internal communications between microservices. Its performance and built-in capabilities make it well-suited for low latency applications.
gRPC Golang example
Let's reimplement examples above using gRPC, starting with defining user.proto
, which be our Protobuf interface
syntax = "proto3";
option go_package="codereliant.io/protobuf/user";
service UserService {
rpc GetUsers(Void) returns (UserList) {}
}
message Void {}
message Empty {}
message User {
int32 id = 1;
string name = 2;
}
message UserList {
repeated User users = 1;
}
More information on how this defined can be found on https://protobuf.dev/
Now we have to generate the grpc interface for both golang (server) and python (client)
$ protoc --python_out=. --grpc_python_out=. protos/user.proto
$ python3 -m grpc_tools.protoc -Iprotos --python_out=. --pyi_out=. --grpc_python_out=. protos/user.proto
This will create multiple generated files, which you will have to add to your projects (e.g. user.pb.go
and user_grpc.pb.go
for Golang). Now let's implement the server:
package main
import (
"context"
"log"
"net"
pb "codereliant.io/protobuf/user" // import generated protobuf code
"google.golang.org/grpc"
)
// Define a server struct that implements the gRPC UserService
type server struct {
pb.UnimplementedUserServiceServer
}
var users = []*pb.User{
{Id: 1, Name: "Alice"},
{Id: 2, Name: "Bob"},
}
// Implement the GetUsers RPC method
func (s *server) GetUsers(ctx context.Context, req *pb.Void) (*pb.UserList, error) {
// Create a new UserList to return
list := &pb.UserList{}
for _, u := range users {
list.Users = append(list.Users, u)
}
return list, nil
}
func main() {
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("Failed to listen: %v", err)
}
// Create a gRPC server
s := grpc.NewServer()
// Register the UserService
pb.RegisterUserServiceServer(s, &server{})
if err := s.Serve(lis); err != nil {
log.Fatalf("Failed to serve: %v", err)
}
}
and client:
import grpc
import user_pb2 # import generated protobuf code
import user_pb2_grpc
# Create a channel to the gRPC server
channel = grpc.insecure_channel('localhost:50051')
# Create a stub (client)
stub = user_pb2_grpc.UserServiceStub(channel)
# Create an empty request
request = user_pb2.Empty()
# Make the call
response = stub.GetUsers(request)
# Print the results
for user in response.users:
print(f"ID: {user.id}, Name: {user.name}")
And Voilà! You have a working gRPC client-server application.
Key Differences
While both are communication protocols, gRPC and REST have some notable differences:
Advantages of REST
REST's simplicity is one of its major benefits. The underlying concepts of resources, representations, and stateless interactions are easy to comprehend. The resources are manipulated using standard HTTP methods that are intuitive. And the widespread use of human-readable formats like JSON and XML also lower the barrier to entry. As a result, even those without a strong programming background can quickly build and consume REST APIs using a myriad of languages and tools.
In addition, REST has been around for much longer and has seen widespread adoption across the software industry. This longevity and popularity has led to a thriving ecosystem surrounding REST. There is plentiful documentation, community support, and tools available for implementing, testing, and managing REST APIs.
When it comes to flexibility, REST does not place many constraints on aspects like protocols, data formats, and architectural style. This flexibility allows REST systems to integrate with each other easily despite differences in technology stacks. REST APIs can accept and produce different data formats like JSON, XML, and YAML to suit various clients. And the focus on manipulating resources enables exposing the same data through different endpoints and actions.
Finally, the stateless nature of REST encourages good scalability and reliability. With no client session data stored on servers, REST systems can scale horizontally by just adding more servers. The server components are also more resilient to failure since not much state needs recovered. REST APIs also take advantage of HTTP caching mechanisms to boost performance. All of this makes designing large, fault-tolerant distributed systems simpler.
Advantages of gRPC
Unlike REST, gRPC offers substantial performance gains thanks to the efficiency of Protocol Buffers. The binary Protocol Buffer format results in much smaller payload sizes compared to equivalent JSON or XML representations. The smaller messages require less bandwidth and allow high throughput communication between services. gRPC also provides native bidirectional streaming support to efficiently transfer large amounts of data when needed. And the code generation from .proto definitions results in serialization and deserialization code that is optimized for speed.
gRPC also simplifies API design through its use of strongly typed schemas and interface contracts. The .proto files act as the single source of truth, defining the service endpoints and request/response message structures. Code generators produce the required stubs and skeletons in target languages for implementing the services and clients. This contract-first approach means the API surface is defined upfront as part of the interface definition. The messages are also statically typed, enabling compile-time type checking and validation. This Protobuf IDL approach enhances correctness and productivity.
Furthermore, gRPC provides first-class support for many languages like Java, Go, C#, Python, Ruby, Node.js, and more. The language-agnostic nature of Protobuf IDLs allows seamless interoperability between gRPC services written in different languages. The code generators for each language abstract away the underlying RPC mechanics. This makes gRPC a good fit for polyglot systems with components written in a mix of languages.
Code Reliability Verdict
For reliability, gRPC has a clear edge over REST and here is why:
- Code generation from IDL definitions reduces boilerplate code and errors
- Strongly typed interfaces and compile-time checking improves type safety
- Standardized error handling model across languages
- Automated client/server stub generation prevents incompatibilities
- Less need for custom middleware to handle common concerns
- Native use of HTTP/2 transfer protocol, which takes cares of things like flow control, cancellation, deadlines, etc
- Bi-directional streaming enables efficient data transfer
That said, REST remains a solid choice for simpler APIs. But for complex services under load, gRPC's baked-in reliability makes development and maintenance much easier.
Member discussion