Categories
Codes GraphQL Node.js

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

“Incredible change happens in your life when you decide to take control of what you do have power over instead of craving control over what you don’t.”

― Steve Maraboli, Life, the Truth, and Being Free

Introduction

In the previous post, we learned about GraphQL queries for getting the data we need. Now, we will get to know about mutations in GraphQL for sending payloads to the server.

If you stumble upon this post first and don’t really understand, please read from the beginning of the “How to Implement GraphQL in Microservices Using Node.js” series:

And as usual, I will provide the source code at the end of the post.

Let’s get started!

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-3. If you want to continue the old one, it’s fine too.
  • 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 do, after cloning the project, run npm i and explore the endpoints defined in index.js to make sure it works well.

Part 3 – GraphQL Mutations

Mutations are queries which require payloads from body to be sent to server. The practice used in mutations are when there are some data-write operations to be done. Pretty similar to HTTP POST, PATCH, and DELETE. In this section, we will create the mutations for creating, updating, and deleting banks.

HttpConnector.js

First, we need to add some functions in connectors/httpConnector.js:

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

const get = async (url) => {...}

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

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

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

module.exports = {
    get,
    post,
    patch,
    remove
}

Other than the get() we created before, we add post(), patch(), remove() as part of httpConnector. The functions are pretty much the same, the only differences are the function called by axios in each of them.

Before we start adding our mutations, let’s make new directories in _root:

  • mutations/index.js
  • mutations/resolvers.js
  • mutations/schema.gql

mutations/index.js is where we import the resolvers and schemas to be imported again later in _root/index.js. Here is a snippet of the code:

const resolvers = require('./resolvers');
const schemas = require('./schemas.gql');

module.exports = {
  resolvers,
  schemas
};

Next, we will register the resolvers and schemas imported from _root/mutations/index.js by modifying our _root/index.js:

// _root/index.js

const queries = require('./queries');
const mutations = require('./mutations');

const rootSchema = ` 
  schema {
    query: Query
    mutation: Mutation
  }
`;

module.exports = {
  resolvers: {
    ...queries.resolvers,
    ...mutations.resolvers,
  },
  schemas: [
    queries.schemas,
    mutations.schemas,
    rootSchema
  ].reduce((acc, types) => { return acc + types })
}

We add mutations.resolvers and mutations.schemas just like the query part.

And you might have noticed the rootSchema here. It’s the same schema from _root/queries/schemas.gql. Because we need to add the mutation also, even though it works, it’s not correct structurally if we modify the schemas in _root/queries.

By adding rootSchema here, we need to modify two things. The first is that we have to register it after mutations.schemas in the same file. Second, we have to remove the schema part from _root/queries/schemas.gql. This must be done for a better structure.

The mutations/resolvers.js and mutations/schema.gql will be modified later, you should already know what these two files will contain though.


Create a BAnk

First, we will update our _root/mutations/schemas.gql to add the schema for our new mutation:

module.exports = `
  type Mutation {
    createBank(name: String!): ID
  }
`;

In this case, we call our mutation createBank because we are creating a new Bank. After that, define the required data and the type of the return value.

Next, we will modify _root/mutations/resolvers.js, to resolve what calling the createBank operation will do:

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

const Mutation = {
  createBank: async (_, data) => {
    return post(`${baseUrl}/banks`, data);
  }
}

module.exports = { Mutation };

Just like _root/queries/resolvers.js, we import axios and getting baseUrl.

Then, we import post() function from httpConnector.js.

Next, we create a Mutation object with a createBank() as the resolver from the schema before. What it does is getting the data to post(), then return the response.

Lastly, export the Mutation with module.exports.

Test the code

Let’s test the code if it works. Run both GraphQL and mock service with npm start. Then, access the GraphQL Playground. In the query tab, put this:

mutation CreateBank ($name: String!) {
  createBank (name: $name)
}

We create a new mutation operation called CreateBank, which requires a name data. Then we query createBank, because we created it in the schema.

Next, in the query variables field, put a JSON object with a property name:

{
  "name": "Bank H"
}

Run the mutation, if it works well, it should show something like this:

Mutation createBank success!

If you are not convinced that it works, you can use QueryBank with the id returned from the mutation. Just like this screenshot:

The data is actually inserted successfully, even though it’s a mock server!

Update a bank

Since we covered much in the previous section, we only need to modify _root/mutations/resolvers.js and _root/mutations/schema.gql by adding the updating parts.

First, let’s start with _root/mutations/schemas.gql by adding updateBank:

module.exports = `
  type Mutation {
    createBank(name: String!): ID
    updateBank(id: ID!, name: String): String
  }
`;

Then, we modify _root/mutations/resolvers.js:

const { post, patch } = require('../../../connectors/httpConnector');

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

const Mutation = {
    createBank: async (_, data) => {...},
    updateBank: async (_, data) => {
        return patch(`${baseUrl}/banks/${data.id}`, data);
    }
}

module.exports = { Mutation };

Before, we created a generic post() function. For updateBank() we import patch() function because the mock service requires PATCH <url>/banks/<bank_id>. Then, in the Mutation object, we add updateBank() to call patch().

Test the code

Run npm start on both GraphQL service and mock service. Then open the GraphQL Playground. In the query field, add this:

mutation UpdateBank ($id:ID!, $name:String) {
  updateBank(id: $id, name: $name)
}

There’s not much difference from createBank. Next, on the query variables field, add this data as JSON object:

{
  "id": "<bank_id>",
  "name": "Bank H"
}

Get the “id” from list of inserted banks. Then you can modify the bank name to anything you want.

Execute the mutation and check if the change is successful. It should look like this:

updateBank operation works.

If you want to be surer, you can check on QueryBank or Banks to see whether the update really happened.


DELETE a bank

Pretty much the same with the previous section: Update a Bank. You just need to copy the “update” part and change it to “remove” and the “patch” to “delete”.

Just as before, add deleteBank in _root/mutations/schemas.gql:

module.exports = `
  type Mutation {
    createBank(name: String!): ID
    updateBank(id: ID!, name: String): String
    deleteBank(id: ID!): String
  }
`;

Then, add deleteBank() as the mutation resolver in _root/mutations/resolvers.js:

const { post, patch, remove } = require('../../../connectors/httpConnector');

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

const Mutation = {
    createBank: async (_, data) => {...},
    updateBank: async (_, data) => {...},
    deleteBank: async (_, data) => {
        return remove(`${baseUrl}/banks/${data.id}`, data);
    }
}

module.exports = { Mutation };
Test the code

Run both services and open the GraphQL Playground. In the query field, write from this snippet:

mutation DeleteBank($id: ID!) {
  deleteBank(id: $id)
}

Then, in the query variables field, we add a JSON object with id only, because the mutation only requires id:

{
  "id":"H161duyov"
}

Run the mutation, and you should see this kind of result if success:

Mutation deleteBank() success!

Lastly, just as before, you can check Banks or QueryBank to make sure the deleted bank is really deleted.

Practice Time!

You should have understood how to make mutations for Banks now. The next step is to try creating Users and Transactions. You can compare your approach with the source code I provide at the end of the post.


Conclusion

By now, you should have learned about:

  • GraphQL mutations
  • Create, update, or delete data to another service

And here is the source code for this tutorial complete with Users and Transactions also, so you can compare your approach with mine.

I hope this helps you in learning GraphQL mutations. Let me know if there’s something unclear or missed!

Next post in the series:
Part 4 – Authentication

“And that is how change happens. One gesture. One person. One moment at a time.”

― Libba Bray, The Sweet Far Thing

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 *