GRPC vs REST on Node.js

Quoting from its website, GRPC is “a high-performance, open-source universal RPC framework”. I needed some convincing myself that GRPC is indeed higher in performance and it is worth the additional effort it takes to implement GRPC.

What exactly is GRPC?

A REST API consists of an HTTP verb which is called by the client. The server responds to the call, usually with a JSON response in the case of APIs. GRPC on the other hand defines a set of communication in the form of a function call in a .proto file. This definition is shared between the server and the client, and the client can call the function as if it is calling a function internally.

An example of a .proto is as follows:

service Greeter {  
  rpc greet(NullRequest) returns (Greeting) {}
  rpc greetWithName(Name) returns (Greeting) {}
}

In this .proto, the service is called Greeter and it has 2 functions greet and greetWithName. The functions also define the input messages NullRequest and Name respectively, as well as the return message Greeting. These definitions are also declared in the same .proto file as follows :

message NullRequest {  
}
message Name {  
  string name = 1;
}
message Greeting {  
  string message = 1;
}

In the message, each field is defined with the type and a running integer. For more details about the .proto, visit the GRPC website.

Is it really faster?

I was curious as to how much faster GRPC is as compared to REST. I wrote a small demo app in Node.js to benchmark the performance. The benchmark tests the performance of HTTP GET and POST requests against similar GRPC based calls. The result:

GRPC#greet x 2,771 ops/sec ±1.62% (78 runs sampled)  
GET#greet x 1,147 ops/sec ±3.78% (73 runs sampled)  
GRPC#greetWithName x 2,539 ops/sec ±3.12% (76 runs sampled)  
POST#greetWithName x 1,054 ops/sec ±2.11% (77 runs sampled)  

2.5 times faster!!

The benchmark was done using benchmark.js with deferred mode on. Although 2.5 times is quite an improvement, there are other benchmarks done in other languages where the performance gain is much higher, so I’m not convinced that my benchmark was done very well.

The face-off

The REST server is a simple Express app with 2 endpoints:

app.get('/greet', (req, res) => {  
  res.json({message: 'Hello world!'})
})
app.post('/greetWithName', (req, res) => {  
  res.json({message: `Hello ${req.body.name}`})
})

The GET endpoint simply responds with a fixed message, while the POST endpoint responds with the request body as part of the message.

For GRPC, there is a server and a client, both implementing the .proto which we have seen earlier.

The server implements greet and greetWithName functions with a callback.

const proto = grpc.load(path.resolve(__dirname + '/greeter.proto'))

class GreetingServer {  
  constructor(port) {
    this.server = new grpc.Server()
    this.handlers = {
      greet: this.greet,
      greetWithName: this.greetWithName
    }
    this.port = port
  }

  greet(call, callback) {
    callback(null, {message: 'Hello world!'})
  }

  greetWithName(call, callback) {
    callback(null, {message: `Hello ${call.request.name}!`})
  }

  start() {
    this.server.addProtoService(proto.Greeter.service, this.handlers)
    this.server.bind(`0.0.0.0:${this.port}`, grpc.ServerCredentials.createInsecure())
    console.log(`GRPC server running on localhost:${this.port}`)
    this.server.start()
  }
}

To call these endpoints, we need to create a client that calls each of the endpoints.

const proto = grpc.load(path.resolve(__dirname + '/greeter.proto'))

class GreetingClient {  
  constructor(port) {
    this.client = new proto.Greeter(`localhost:${port}`, grpc.credentials.createInsecure())
  }

  greet(callback) {
    this.client.greet({}, (err, res) => {
      if (err) {
        console.log(err)
      }
      callback(null, res)
    })
  }

  greetWithName(name, callback) {
    this.client.greetWithName({name: name}, (err, res) => {
      if (err) {
        console.log(err)
      }
      callback(null, res)
    })
  }
}

To perform the benchmark, I created a benchmark.js test suite which calls all 4 endpoints, using either Node.js HTTP agent or the GRPC client created earlier.

Is GRPC worth it?

Implementing GRPC comes with an additional overhead of maintaining a .proto definition and sharing it across services. My team at work still have not found an elegant solution to this, or rather have not had the time to really look into a solution. Beyond this hassle, GRPC is a promising solution for backend communication between services.

Albert Salim

Software developer at ThoughtWorks, part time triathlete, occasional photographer.

Subscribe to Albert Salim

Get the latest posts delivered right to your inbox.

or subscribe via RSS with Feedly!