Categories
Codes GraphQL gRPC Node.js

Setting Up GraphQL Server as gRPC Client

“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:

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:

Connecting to gRPC works!

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

By Ericko Yap

Just a guy who is obsessed to improve himself. Working as a programmer in a digital banking company. Currently programming himself in calisthenics, reading books, and maintaining a blog.

Leave a Reply

Your email address will not be published. Required fields are marked *