“If you want to make beautiful music, you must play the black and the white notes together.”
― Richard Nixon
Introduction
In the previous posts, you have learned how to connect to other microservice from a GraphQL server. The other microservice ran on REST API which you know, the usual stuff.
This time, we are going to connect the GraphQL server with a gRPC server. It’s not that complicated though, what we need to change is the resolvers to act as a gRPC client which will do a remote procedure call (RPC) to the gRPC server.
To make things simple, I will provide the working source code of the gRPC server. What we are going to learn here is about modifying the resolver connecting to a gRPC server from GraphQL.
Before starting, it is recommended that you have understood the concepts of GraphQL and gRPC. If you don’t, you can read them here:
Prerequisites
You can get these source codes for reference to build this project:
- GraphQL Server
This contains the source code of a working GraphQL server. You can find out how it works here. - gRPC Simple Client & Server
The source code of a gRPC client and gRPC server. Find out how it works here.
I’m going to name the project in this post as cil-nodejs-gql-grpc
. And I will share the source code at the end of the post.
Initiate gRPC Client
I assume that you start with cloning the GraphQL Server, so you need to install some libraries with npm i
:
- @grpc/grpc-js
- @grpc/proto-loader
Then, because we are adding a gRPC module here, let’s create a new directory src/grpc
.
grpc/initiateGRPCClient.js
This file is similar to the gRPC client we created in the post before. The differences are that we are going to make this module generic and reusable without any dependence on hard-coded variables. See this snippet below:
const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');
const initializeGRPCClient = (server, protoConfig) => {
const { path, packageName, serviceName } = protoConfig;
const { host, port } = server;
const target = `${host}:${port}`;
const packageDefinition = protoLoader.loadSync(
path,
{
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true
}
);
const pkg = grpc.
loadPackageDefinition(packageDefinition);
const proto = pkg[`${packageName}`];
const Client = proto[`${serviceName}`];
const client = new Client(target,
grpc.credentials.createInsecure());
return client;
}
module.exports = initializeGRPCClient;
First, we load the required libraries @grpc/grpc-js
and @grpc/proto-loader
.
Then, we create a function which requires the necessary configurations for now: server
which includes host
and port
, and protoConfig
which includes path
, packageName
, and serviceName
.
Next, we load the packageDefinition
with protoLoader.loadSync()
. path
is required as the first argument here.
After getting the packageDefinition
, load it with grpc.loadPackageDefinition()
. Then, get proto
from calling pkg[`${packageName}`]
, which result will be required to get Client
using proto[`${serviceName}`]
.
Lastly create a new Client
instance and return it.
Remember to also export the module with module.exports
as usual.
grpc/index.js
So, where do we execute initiateGRPCClient
module? From an index.js
. Here, we will load the configuration for creating a gRPC client. The snippet below shows how:
const initGrpcClient = require('./initializeGRPCClient');
const server = { host: 'localhost', port: '50051' };
const banks = initGrpcClient(
server,
{ path: __dirname + '/protos/banks.proto', packageName: 'banks', serviceName: 'Banks' }
);
module.exports = {
client: {
banks
}
};
This module is pretty straightforward in my opinion. First, we load the initiateGRPCClient
module. Then, we call the module to create a banks
client. Lastly, export the loaded client inside a client
object.
So, if you need to add more gRPC clients with other configurations, you can do it in this file.
grpc/protos/banks.proto
Remember the proto file we created before?
The same proto file will be used here, so just copy and paste it from before to grpc/protos
directory.
index.js
Let’s load the gRPC module so it can be loaded as context of the GraphQL server. See the snippet below:
// index.js
...
const graphql = require('./graphql')
const grpc = require('./grpc');
const context = ({ req }) => {
return {
headers: req.headers,
grpc
};
};
const server = new ApolloServer({
...graphql,
context
});
const app = express();
server.applyMiddleware({ app });
...
First, we load the grpc
module from ./grpc
.
Then, we create a context
function which returns data which will be used in the context such as headers
. grpc
is one of those, so put it as one of the response.
Lastly, we create an ApolloServer
to enable GraphQL service. The argument is an object which consist of everything loaded in graphql
module, and the context
function.
Modify Resolver Logic
Now that we can connect to the gRPC server, let’s modify the resolver logic to execute the operations through the gRPC server instead of using Express.
baseResolver.js
The resolvers in the GraphQL server is based on baseResolver.js
. So, we will create a function to create a resolver for operations to the gRPC server. You can see the snippet below:
// src/graphql/baseConnector.js
...
const createRPC =
(serviceName, rpcName, targetPropName = '') => {
return async (_, data, context) => {
const { grpc: { client } } = context;
const rpcClient = client[`${serviceName}`];
return new Promise((resolve) => {
rpcClient[`${rpcName}`](data, (err, response) => {
if (targetPropName) {
resolve(err || response[`${targetPropName}`]);
}
else {
resolve(err || response);
}
});
});
}
};
module.exports = {
...,
createRPC
}
Let’s add a function called createRPC()
. This function will only be the only function to create a resolver to the gRPC server.
It has three parameters: serviceName
, rpcName
, and targetPropName
. You can think of serviceName
as class, and rpcName
is the function inside the class. targetPropName
is the name of the property from the returned object if the operation is successful.
Same as the other functions in this file, createRPC()
will return an asynchronous GraphQL resolver function. Inside the resolver function, we create a generic function which calls the remote function from the gRPC server.
First, we need to get the client
object, which is put inside context.grpc
.
Next, because there are multiple clients (in the source code I will share later), we need to define which client we will get using client[`${serviceName}`]
.
And then, we will return a promise because it is an asynchronous function.
Inside the asynchronous function, we call the RPC we need. There are two main arguments here: data
and the callback function.
You can easily pass data
from the parameter of the resolver function.
For the callback function, we differentiate between whether targetPropName
is defined or not. Should targetPropName
exists, resolve the response
by specifying the targetPropName
. If not, just resolve the response
.
Lastly, remember to export createRPC
using module.exports
.
_root/queries/resolvers.js
As an example to implement createRPC
, let’s change one of the resolver function in _root/queries/resolvers.js
. See this snippet below:
const { ..., createRPC } = require('../../baseResolver');
const Query = {
...,
// Express server
// banks: createGetListUrl(`banks`),
// gRPC server
banks: createRPC('banks', 'getAllBanks', 'listBank'),
...
};
module.exports = { Query };
First, we import the createRPC
module from baseResolver
.
Then, we simply call createRPC
as the resolver for banks
. The function needs three arguments. The first one is serviceName
which we will put banks
. Next, the second argument is rpcName
, we will put getAllBanks
. Last one is targetPropName
, put listBank
in there.
Code Testing
OK, now we check if the code works.
On the gRPC server, run npm run start:grpc-server
. Then, on the GraphQL server, run npm start
.
Open the GraphQL Playground, and we will try to get a list of banks with this query:
query Banks {
banks{
id
name
}
}
Run the query, if everything goes well, it should show something like this:

Well, it does not seem different with the Express version, because we change only the insides from Express to gRPC.
If you want more proof that it works, you can always add something from the mock DB in the gRPC server and get the data again. Or you can add some logging in createRPC
or in the handlers of gRPC server.
Practice Time!
If you manage to succeed in making get bank list works, you should practice resolving the others such as users
and transactions
including all the CRUD operations. Which means, the createRPC
function can be used for resolving mutations too.
Conclusion
So, these are the things you should have learned, or at least understood, about connecting a GraphQL server to gRPC server using Node.js:
- Make a gRPC client in a GraphQL server
- Modify the resolver logic so it connects to the gRPC server.
And, here is the source code I promised earlier. Did you miss anything important?
I hope this can be a help to you, especially if you plan to learn or implement gRPC in your projects!
See you in the next post!
“Your business and your life are happening at the same time. Don’t separate them. Integrate them.”
― David Taylor-Klaus