The world of microservices is unforgiving. When you can’t communicate and do it fast, you don’t belong. To relieve this situation, developers employ remote procedure call systems, which make it easier to connect services, devices and browser clients, regardless of how different from each other they are. HTTP/2-based gRPC is one of the latest attempts at getting it right. Is it a successful one? Read our practical gRPC tutorial and see for yourself.
Applications and devices nowadays are pretty much useless without the ability to send and receive data. More than that – they are expected to do it fast and efficiently. A typical backend system is no longer a huge monolith, where all the communication takes place inside a single process, but instead consists of dozens of microservices which must communicate somehow. There are many ways and architectural patterns to solve these problems – we have REST APIs, graphQL, web sockets, message buses, just to name a few.
Today I’d like to give you a quick and practical gRPC tutorial. I have adopted this solution as a way for microservices to talk to each other. I will you show the gist of how it works.
Why should I care about remote procedure call systems and gRPC?
gRPC is a remote procedure call (RPC) framework built by Google. It’s open-source, free-to-use and available on many different development platforms. At the time of writing, they include C++, Java, Python, Ruby, Go, C#, PHP, Dart and of course Node.js. It’s available on mobile platforms as well. Since recently, there’s also a ready-made gRPC solution that allows you to use it in the browser (although not natively, you still have to use a proxy).
gRPC embraces HTTP/2, allows for client-server and duplex streaming of data and relies on protocol buffers as a serialization mechanism that allows it to be efficient as well as much easier on the CPU. This quality is especially valuable on mobile devices (lower battery usage). gRPC shines in low-latency and highly distributed systems such as microservices due to its efficiency, but also because of the RPC paradigm (we call remote methods the same way we’d call normal methods – if we use some abstractions on top of gRPC, our developers might not even be aware that the code uses gRPC under the hood) is specifically made to work well in such an environment.
Disadvantages of the gRPC approach
It’s worth it to keep in mind that using gRPC is not all roses. The technology is still relatively young, which means that you can’t expect an endless ocean of resources on it – the way you could with REST. Don’t get me wrong – the official docs is fine and there’s quite a lot on the web about gRPC. It’s just that sometimes you’ll have to dig just a little harder. Still, as gRPC is gaining more and more traction, the situation will be getting better every day.
Another thing is the tooling. It isn’t quite as good and easy as for the good ol’ JSON over HTTP. For example, if we’re given a REST endpoint that returns some data, such as GET /users, we can simply open it in a browser and we’ll see the result. If we need something more sophisticated, we can quickly open up Postman or curl. With gRPC, it isn’t quite as straightforward.
What’s more, gRPC doesn’t really work natively in the browser. However, you can build your own UI-Gateway that might expose some WebSocket on one end and talk with gRPC on the other. There’s also this gRPC web project I mentioned earlier. Just keep in mind that there is no out-of-the-box browser support for gRPC.
See also: Serverless in Node.js: Beginner’s guide
gRPC in action – a practical example
While theory is surely useful, it’s also quite boring – nothing beats coding. So let’s jump straight to it. We’ll mix some theory in when needed – mostly when we encounter a new concept or term.
Prerequisites for a gRPC-based project
The only thing you really need is Node.js installed on your machine, preferably the active LTS version. If you use VS Code, you might consider installing vscode-proto3 extension. It may come in handy when working with protobuffers. Syntax highlighting and validation might save you some headaches when your application doesn’t want to start because of some typos in your protobuff files.
gRPC’s protocol buffers
First, let’s define our messages and service definitions containing the signatures of RPC methods in a protocol buffer. They are just another way of serializing structured data. In the official docs, they’re described as “think XML, but smaller, faster, and simpler”. Sounds promising, doesn’t it? Let’s create one, then:
From the top, we first defined the version of protocol buffers we’ll be using. Then, we defined our requests and responses, and finally added a service definition. Our service consists of two methods:
- joinChat – a server-side streaming method (notice the stream keyword in its definition). It means that once client sends a single request to the server, it will receive a stream of responses over time.
- sendMessage – a simple unary method that sends a single request and receives a single response (should be quite familiar to anyone who has ever worked with REST APIs)
gRPC also allows for client and duplex streaming methods. In the first case, it’s the client that streams messages to the server. In the latter, both client and server stream messages independently.
gRPC will use our proto to generate a stub (also called a client) and an empty server, where we’ll add our service and provide handlers with an actual implementation.
Bootstrap the app and off we go!
Before we continue, let’s initialise our project and install some necessary packages:
Now, let’s create two empty files – server.js and client.js. Our project structure should look like this:
We can finally start implementing our server! Let’s open server.js and add the following code:
We loaded a proto file and created an empty server based on the service definition. To see that our service has been added to the gRPC server, we can start it:
… and we’ll see a message that we need to implement the two methods defined in our service definition:
Method handler joinChat for /ChatService/joinChat expected but not provided
Method handler sendMessage for /ChatService/sendMessage expected but not provided
Note that we provided credentials when creating the server:
What we’re saying here is that we won’t be authenticating clients in any way and anyone who is able to connect to our server can call its methods. It also means that the data sent between the server and clients won’t be encrypted. gRPC provides different authentication mechanisms that can help solve these problems, but for the sake of simplicity we won’t be covering them here.
You’ve probably also noticed that we created a variable usersInChat that stores all the users that have joined the chat – when a new message arrives, we’ll broadcast it to all of them.
We can now implement our server handlers. Let’s remove the comment at line 12 and provide two handlers:
We need to add them to our server as well. Let’s add this line under our server definition:
Let’s run the server again. Since the service methods are now implemented, it doesn’t complain anymore. Bad news is that we only provided an empty implementation. You have to agree that it’s not particularly useful. Let’s fix it. We can start by implementing the joinChat method:
We have now added a mechanism that allows new users to join our chat. We’re also handling an event that will occur when the client cancels the connection. In this case, we want to remove such a user from our collection. There’s definitely no point in sending messages to them anymore.
Next, let’s implement our sendMessage handler:
When the new message arrives, we first check if it’s not empty. If it is, we return an error to the caller. In unary calls, such as our sendMessage, it is done by providing an error as the first argument of the callback.
If the validation passes, we can build a new message by extending the original one with a timestamp. Once the message is built, we can broadcast it to all the users in the chat. Finally, we return an empty object as an response (that’s what we have defined in our proto).
That’s pretty much it when it comes to implementing the server. We can keep it up and running now and move on to implementing the client that can talk with it.
First, we open our client.js file. Let’s start again with the code responsible for loading the gRPC package definition from a proto file (We want to focus on gRPC mechanisms, so we’ll simply copy the first few lines from the server. Let’s forget momentarily about the DRY principle for simplicity’s sake). At this point, we can create our client.
The client is based on our proto service, so it exposes the same methods – joinChat and sendMessage. Let’s implement the rest of the client by calling them:
The interesting bit here is what happens when we’re calling joinChat. While a normal unary request, like sendMessage, reads the single response from the server via a callback, joinChat returns a stream of messages. As a result, we’re given an object that we can use to subscribe to an event that will happen each time a new piece of data arrives. On that event, we (slightly) format our message and display it.
Aside from that, everything should be pretty straightforward. After we join the chat, we can start sending messages and receive them back on the data event.
Let’s ensure the server is running and run the client:
If you’ve completed every step correctly, you should start seeing messages in the console.
Practical gRPC tutorial – summary
That’s it for our short app. Hopefully, I have given you some insight on how to get started with gRPC in Node.js. I haven’t shown some interesting aspects of gRPC such duplex streaming or authentication, but hopefully it was enough to encourage you to keep exploring the topic and give gRPC a try in a real project. While gRPC is still far from perfect, it does have a lot of potential. At this point, I most definitely don’t regret using it. As it’s getting more and more traction, it seems it’s here to stay – especially in the microservice universe. And that’s the universe we’re living in nowadays.