Categories
Codes GraphQL Javascript Node.js

How to Implement GraphQL in Microservices Using Node.js – Part 2

“You will never get everything in life but you will get enough.”

― Sanhita Baruah

Introduction

In the previous tutorial, you have learned about setting up a GraphQL server with a structure which enables you to add more types easily. Should you not, please visit this post before continue reading.

I will share about creating queries in a GraphQL server. And we will emulate the environment of microservices with the GraphQL server as the API gateway. Then the GraphQL server will connect to those microservices to do the operations.

If you think there will be lots of code, well it will be. So, I will provide the source code for the other services at the beginning so you can run it to test your GraphQL server code locally. But, the for the GraphQL server code will be shared at the end of the post.

Now, let’s begin.

Requirements

  • The previous GQL project
    It’s recommended to clone this project we created from the previous tutorial, and name it as cli-nodejs-gql-2
  • This mock service project
    If you prefer to build another REST API on your own, you are free to do so. But, why not save time? If you wish to do so, after cloning the project, run npm i and explore the endpoints defined in index.js to make sure it works well.

Part 2 – GraphQL Queries

Suppose we have an interbank network system. The system connects all banks in the country, enabling the users to do transactions from and to any banks in real time.

The admin’s task is to manage new and existing users, and the list of banks in the system. This admin is very trustworthy that he has the power to add or deduct the users’ balance. The only thing he cannot do is changing the transactions that has already been done.

We are given the task to create an API which fulfils the admin’s requirements, because we don’t want the admin to be human. He would become too powerful with his privileges.

Install Dependencies

Before we start, we need to install axios, a promise-based HTTP client for browser and Node.js. Axios will be used for fetching data from the mock service. You can install axios by running npm i axios on the root of the GQL project.

Queries

To put it simply, queries are for getting the data you want using the GraphQL syntax. The usage is similar to HTTP GET. In this section, we will create some queries for getting a bank or list of banks.

First, we create a new feature called banks. Set the directories structure similar as the helloworld we refactored in the section before. In other words, copy the helloworld directory and rename it as banks.

Then, on /graphql/index.js, add banks feature, just like the snippet below:

// /graphql/index.js

const { gql } = require('apollo-server-express');
const root = require('./_root');
const helloworld = require('./helloworld');
const banks = require('./banks');

const typeDefs = gql`${[
    root.schemas,
    helloworld.schemas,
    banks.schemas
].reduce((acc, types) => { return acc + types })}`;

const resolvers = {    
    ...root.resolvers,
    ...helloworld.resolvers,
    ...banks.schemas
};

module.exports = {
    typeDefs,
    resolvers
};

Get banks

Open _root/queries/schemas.gql, and add banks as Array:

module.exports = `
  type Query {
    helloworld: HelloWorld
    banks: [Bank!]
  }

  schema {
    query: Query
  }
`;

The banks query will be returned as an array and it can also be an empty array. But, the contents of the array must be from the Bank schema which we will add right now.

So, we add more details on Bank in /banks/queries/schemas.gql, to define which properties we can fetch from the database. It’s not much, but will do for now:

module.exports = `
  type Bank {
    id: ID
    name: String
  }
`;

The Bank has two properties: id with the ID type and name with String type. Both properties could be null.

On /banks/queries/resolvers.js, there isn’t anything to add, but for structure’s sake, we will add these:

// banks/queries/resolvers.js
const Bank = {};

module.exports = { Bank };

Then, back to the _root directory, on _root/queries/resolvers.js, we add the resolver to get banks by using axios:

const axios = require('axios');
const baseUrl = 'http://localhost:4001';

const get = async (url) => {
  try {
    const response = await axios.get(url);
    return response.data;
  } catch (error) {
    console.error(error);
    throw new Error(error);
  }
  return null;
}

const Query = {
  helloworld: () => 'helloworld',
  banks: async () => await get(`${baseUrl}/banks`) || []
};

module.exports = { Query };

First, we import the axios library. Then, set the baseUrl of the external service we will get the data from. After that, we write the get() function to fetch data. Using axios means that the response data will be inside the data property, so we return response.data. Remember to add a try-catch also. Lastly, add banks as a function of Query, which calls the get() function with the URL we provide. If somehow the function fails, we can still return an empty array, just as defined in the schema.

Test the code

And now, we test the code. Run both cil-nodejs-gql-2 and cil-nodejs-gql-2-ms with npm start each. If you still use the same http://localhost:4000/graphql, open it and run the GraphQL Playground. We want to get a bank’s list, so input this query:

query {
  banks {
    id
    name
  }
}

If everything works well on both services, it should show something like this:

Querying a list of registered banks.

Get A Bank

Open _root/queries/schemas.gql, and add bank with a parameter id, so we know which specific bank to fetch later on:

module.exports = `
  type Query {
    helloworld: HelloWorld
    banks: [Bank!]
    bank(id: ID!): Bank
  }

  schema {
    query: Query
  }
`;

Next, because there’s not any difference in /banks/queries/schemas.gql and /banks/queries/resolvers.js, we will skip them and modify _root/queries/resolvers.js to add what happens when we fetch bank in the query:

// _root/queries/resolvers.js
...
const get = async (url) => {...}

const Query = {
  ...,
  banks: async () => await get(`${baseUrl}/banks`) || [],
  bank: async (_, params) => await get(`${baseUrl}/banks/${params.id}`) || {}
};

module.exports = { Query };

We add the bank resolver in the Query object. As you might have noticed, there are two parameters in the bank function. The first parameter is root, but we can ignore it for now and we write it as _. But, what we need is the second parameter params. We want the id parameter we requested to get the singular bank. Lastly, we fetch the data using the get() function we created before.

test the code

What’s left is to test the code. So, run both the GQL and mock services. Then, access the GraphQL Playground. Get an id from the banks list from the query before, and run this query in a new tab:

query Bank ($id: ID!) {
  bank (id: $id){
    id
    name
  }
}

We name the query as Bank, but it’s OK if you want to name it as you like. And it shows that it needs id from $id. The $id will be obtained from a JSON object defined in the query variables.

So, add a query variable id in a JSON object (The id value could be anything though):

{
  "id": "BJxBKwccP"
}

It should show this kind of result if it fetches succesfully:

Successfully query a bank.
Practice TIME!

By now, you should have understood the structure and pattern to add a query operation in GraphQL. As practice, you can try adding Users and Transactions using the same pattern as the example sections above.

In the source code provided later, I will also include both Users and Transactions, so you can compare both approaches.


Refactor HTTP Module

Just a little bit of refactor for the sake of cleanliness. We will separate the HTTP module from the root resolvers in queries. So, we can load axios from that HTTP module only, then import them to be used in the query resolvers. And the mutation also later on.

HttpConnector

First, we create the <root>/connectors/httpConnector.js file as the new HTTP module:

// <root>/connectors/httpConnector.js
const axios = require('axios');

const get = async (url) => {
    try {
        const response = await axios.get(url);
        return response.data;
    } catch (error) {
        console.error(error);
        throw new Error(error);
    }
    return null;
}

module.exports = {
    get
}

All the functions in the file are taken directly from _root/queries/resolvers.js. Remember to add the axios library. Then, export the module at the end of the file.

Resolvers in _root/queries

Because we have created a httpConnector, the same functions in _root/queries/resolvers.js can be removed and be imported from httpConnector.js.

First, we modify _root/queries/resolvers.js:

const { get } = require('../../../connectors/httpConnector');

const baseUrl = 'http://localhost:4001';

const Query = {
  helloworld: () => 'helloworld',
  banks: async () => await get(`${baseUrl}/banks`) || [],
  bank: async (_, params) => await get(`${baseUrl}/banks/${params.id}`) || {}
};

module.exports = { Query };

We import get() from httpConnector.js and remove the existing get() before. It looks cleaner now, right?

Test the code

Run npm start on both services and test all the endpoints to make sure everything still works.


Getting Bank Detail From User

If you create the query of User, you should notice that the User data only has bankId. The admin might not know what the related bankId means, so we have to provide the bankId with more data such as the bank name.

Assuming you have created Users query from the previous practice section, you would have this schema for User in users/queries/schema.gql:

module.exports = `
  type User {
    id: ID!
    name: String!
    bankId: String!
    amount: String!
  }
`;

We will change the bankId to bank, so humans could understand what bankId is for. And bank is the type of Bank:

module.exports = `
  type User {
    id: ID!
    name: String!
    bank: Bank!
    amount: String!
  }
`;

Next, we modify users/queries/resolvers.js to resolve how we can get Bank from bankId:

const { get } = require('../../../connectors/httpConnector');
const baseUrl = 'http://localhost:4001';

const User = {
    bank: async (user) => await get(`${baseUrl}/banks/${user.bankId}`) || {}
};

module.exports = { User };

First, we import get() from httpConnector, because we need to get() the Bank data from our mock service. Then, in the object User, we set a bank function as the resolver.

The function is asynchronous and require a parameter called user. And this user has the property of bankId, if you check the data set from the mock service. Basically, it’s an unmodified user data obtained from the database. Because what we need is bank, not bankId, we will use the user.bankId to get the data. Lastly, we call the get() function to get the data from the mock service.

Test the Code

Run both services with npm start. Then create a new User query, including bank:

query User($id: ID!) {
  user(id:$id) {
    id
    name
    bank {
      id
      name
    }
    amount
  }
}

In the query variables field, provide id as parameter:

{
  "id":"<user_id>"
}

Run the query. And you should see something like this:

Query User: Success adding bank as detail of user data.

If you run the Users (the list one) query, it should be like this:

Query Users: Success showing bank detail for every user.
Practice Time!

The bank can be changed to bankName if you want to. You just have to return bank.name after getting the data from the user resolver. Try it out.

And now, you should be able to add the query for Transactions detail for humans usage, like getting the bankName before.

Conclusion

We have learned much this time also. By now, you should have learned about:

  • GraphQL queries
  • Connecting a GraphQL server to another service
  • Get data from another service
  • Create query resolvers

And here is the source code for this tutorial.

Same excuse with the previous post, this got too long so I have to separate the mutations part for another post.

I hope this will help you understand queries in GraphQL. If I missed something, let me know in the comments!

Next post in the series:
Part 3 – Mutations

“Enough will never be enough for some. So be enough for yourself instead.”

― Christine E. Szymanski

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 *