gRPC 101 with Docker, NodeJS and Express
This is one of the simplest examples for diving into gRPC, a remote procedure call framework with the usage of Node.js gRPC API for building client and server. Our project includes Dockerfile separately for client, server and a single docker-compose file for running both of the services. But you can adjust the architecture as per your need.
Diving into …
We have a proto file for definition of service Greeter
hello.proto
syntax = "proto3";package helloworld;service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}message HelloRequest {
required string name = 1;
}message HelloReply {
required string message = 1;
}
There is a RPC method named SayHello
that takesHelloRequest
parameter from nodejs client and returns HelloReply
from nodejs server. Here, we define hello request and hello reply message where both are string corresponding to HelloRequest
and HelloReply
. We define the messages as required
but that could be optional. Each field in the message should have name and type. Simple as that.
On the server side
We have implemented gRPC server on server.js
file inside server
folder which reference proto file hello.proto
and import grpc module. gRPC Protobuf Loader is used to load the proto file. We have sayHello
rpc method which handles the operation request and we bind the server to port 50051
.
server.js:
var PROTO_PATH = __dirname + '/hello.proto';
var grpc = require('grpc');
var protoLoader = require('@grpc/proto-loader');
var packageDefinition = protoLoader.loadSync(
PROTO_PATH,
{keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true
});
var hello_proto = grpc.loadPackageDefinition(packageDefinition).helloworld;function sayHello(call, callback) {
callback(null, {message: 'Hello ' + call.request.name});
}function main() {
var server = new grpc.Server();
server.addService(hello_proto.Greeter.service, {sayHello: sayHello});
server.bind('0.0.0.0:50051', grpc.ServerCredentials.createInsecure());
server.start();
}main();
And finally, Dockerfile for getting packages and running server
Dockerfile:
FROM node:11-alpine
COPY ./ /app/
WORKDIR /app
RUN npm install
CMD ["node", "server.js"]
Client time
Our client uses express server which handles GET request binding to port 3000.
client-express.js:
var PROTO_PATH = __dirname + '/hello.proto';var grpc = require('grpc');
var protoLoader = require('@grpc/proto-loader');
var packageDefinition = protoLoader.loadSync(
PROTO_PATH,
{keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true
});
var hello_proto = grpc.loadPackageDefinition(packageDefinition).helloworld;const express = require('express')
const app = express()
const port = 3000function main() {
var client = new hello_proto.Greeter('grpc-server:50051', grpc.credentials.createInsecure());
var user;
app.get('/:id', function (req, res) {
user = req.params.id;
client.sayHello({name: user}, function(err, response) {
console.log('Greeting:', response.message);
res.send(response.message);
});
});
app.listen(port, () => console.log(`Example app listening on port ${port}!`))
}
main();
Dockerfile:
FROM node:11-alpine
COPY ./ /app/
WORKDIR /app
RUN npm install
CMD ["node", "client-express.js"]
Docker Compose for binding server and client
We have 2 services: grpc-server and grpc-client with container names which uses Dockerfile of respective folder for building image and running the services.
version: "3"
services:
grpc-server:
container_name: grpc-server
build:
context: ./server
dockerfile: Dockerfile grpc-client:
container_name: grpc-client
build:
context: ./client
dockerfile: Dockerfile
ports:
- "3000:3000"
Port 3000 is mapped so that we can call the client from localhost.
Get the services running:
$ docker-compose up -d
Check the logs
$ docker-compose logs -f
Finally, send request to client
$ curl localhost:3000/raju
On the logs, we can see response
Attaching to grpc-client, grpc-server
grpc-client | Example app listening on port 3000!
grpc-client | Greeting: Hello raju
By this way we can build microservices using grpc and docker which eases the process of building applications by implementing docker.
In my next post, we will talk about the deployment of these services on Kubernetes. Stay tuned.