Categories
Codes Javascript Node.js

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

“Take only what you need, not what you want.”

― Forrest Carter

Introduction

In this first part of “How to Implement GraphQL in Microservices Using Node.js” series, , I will share about creating a GraphQL server which connects to the REST API service we created before. It also includes of what I think is a good structure to create a GraphQL Server which supports multiple features.

I will share the project through GitHub at the end of the post as usual.

Let’s get started.

What is GraphQL?

To quote from the official website of GraphQL:

GraphQL is a query language for your API, and a server-side runtime for executing queries by using a type system you define for your data. GraphQL isn’t tied to any specific database or storage engine and is instead backed by your existing code and data.

What is GraphQL?

Or, in simpler words I hope, you send a query to get the data you need from the provided data. Let’s say a row from database has 8 properties. With GraphQL, you can get from 1-8 properties from the data by requesting the property names you need in the query.

The result is you will get the data with only the properties you requested and the other not requested properties are ignored.

Why GraphQL?

GraphQL helps clients to fetch the right amount of data needed to render the view. Because it lets the client-side to define the response of a request, it’s also able to reduce the bandwidth needed on fetching the response.

Additionally, it removes the complexity of managing endpoints of a REST API. The usual practice is by creating a new HTTP endpoint (usually /graphql) to fetch the required data.

Requirements

If you don’t have Node.js installed yet, here are some links for you:

  • Node.js
    I recommend download the LTS one. Because, well, longer term support. To make sure it works, type node -v on your Terminal after installation:
  • Postman or Insomnia
    To check whether your API is working. Personally, I prefer Postman because of the multiple tabs. To know it’s working, well, just open the app.

Part 1 – Create GraphQL Server

Create a New Express Service

I will not go into details here. Please refer to this tutorial when creating a new service. Name the service as cli-nodejs-gql. And by the time this tutorial is written, I use Node 12.

Install Required Libraries

After initiating the new project, install these libraries:

Hello World In The Style of GraphQL

Create a new file named index.js, which contains this snippet:

// index.js

const express = require('express');
const { ApolloServer, gql } = require('apollo-server-express');

// Construct a schema, using GraphQL schema language
const typeDefs = gql`
  type Query {
    hello: String
  }
`;

// Provide resolver functions for your schema fields
const resolvers = {
    Query: {
        hello: () => 'Hello world!',
    },
};

const server = new ApolloServer({ typeDefs, resolvers });

const app = express();
server.applyMiddleware({ app });

app.listen({ port: 4000 }, () =>
    console.log(`? Server ready at http://localhost:4000${server.graphqlPath}`)
);

The code will be explained on the next sections. Let’s make sure the configuration works for now.

Run the service with npm start or node index.js. It should look like this if it ran successfully:

GraphQL server is ready!

Then, run the Postman app and access http://localhost:4000/graphql. You can also access the GraphQL Playground by entering the URL to your web browser.

  • If you prefer Postman, on the body sub-tab, pick GraphQL, and navigate to the query window.
  • If you prefer the Playground, directly navigate to the left text area field.

Whichever you choose, write this query:

{
  hello
}

And leave the GraphQL variables for now. Lastly, click Send to get the results. If everything works well, it should be like this screenshot below:

Hello World!

Restructuring Hello World

The “Hello World” works, but I want to make the code easier to read and maintain. If I want to add a new module, I would not stack all the queries and mutations in index.js, as it would make the file super long and difficult to trace.

Another reason is, I don’t want to make another refactoring post.

So, let’s create some new directories:

  • <project-root>/graphql
  • <project-root>/graphql/helloworld
  • <project-root>/graphql/helloworld/queries

Then, add some empty files:

  • <project-root>/graphql:
    • index.js
  • <project-root>/graphql/helloworld:
    • index.js
  • <project-root>/graphql/helloworld/queries:
    • index.js
    • resolvers.js
    • schemas.gql
Resolvers.js

Let’s start from moving the resolvers part from <root>/index.js to /graphql/helloworld/queriers/resolvers.js. Add this part from index.js without changing anything and and module.exports at the end of the file:

// <project-root>/graphql/helloworld/queries/resolvers.js

const resolvers = {
    Query: {
        hello: () => 'Hello world!'
    }
};

module.exports = resolvers;
Schemas.gql

Next, we will add schemas.gql. In this file, we write the type definition and export it with module.exports. See this snippet below:

// <project-root>/graphql/helloworld/queries/schemas.gql

module.exports = `
  type Query {
    hello: String
  }
`;
/helloworld/queries/index.js

Then, we import both the resolvers and schemas modules into /helloworld/queries/index.js:

// /helloworld/queries/index.js

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

module.exports = {
    resolvers,
    schemas
};
/graphql/helloworld/index.js

Another index.js file, but this one’s in the /graphql/helloworld directory. This module imports all resolvers and schemas defined in queries and (later on if needed) mutations. After importing the modules, it merges them into one resolvers and schemas. See this snippet below:

// /graphql/helloworld/index.js

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

// if the helloworld have mutations, add them here also.
// const mutations = require('./mutations');

module.exports = {
    resolvers: {
        ...queries.resolvers
        //...mutations.resolvers
    },
    schemas: [
        queries.schemas,
        // mutations.schemas
    ].reduce((acc, types) => { return acc + types })
};
/graphql/index.js

The next step is to import all features existing in the project. We do this in /graphql/index.js. After importing, this module merges all resolvers and schemas already defined in each features before. The helloworld is a feature and thus, we will import helloworld to this file:

// /graphql/index.js

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

const typeDefs = gql`${[
    helloworld.schemas,
    // feature_a.schemas,
    // feature_b.schemas,
    // feature_c.schemas
].reduce((acc, types) => { return acc + types })}`;

const resolvers = {
    ...helloworld.resolvers,
    // feature_a.resolvers,
    // feature_b.resolvers,
    // feature_c.resolvers
};

module.exports = {
    typeDefs,
    resolvers
};

Remember to import { gql } from apollo-server-express library to convert the type definition strings as GraphQL.

index.js

OK, this is the final index.js. We will remove the typeDefs and resolvers from this file. And then, we will import the graphql module and then initialize the ApolloServer with it. See this snippet below:

const express = require('express');
const { ApolloServer, gql } = require('apollo-server-express');
const graphql = require('./graphql')

const server = new ApolloServer(graphql);

const app = express();
server.applyMiddleware({ app });

app.listen({ port: 4000 }, () =>
    console.log(`? Server ready at http://localhost:4000${server.graphqlPath}`)
);

Much simpler now.

Test the code

Run npm start and try to hit the endpoint localhost:4000/graphql with the same configurations from the previous “Hello World” section. If everything goes well, it will give the same result.


Preparing To Support Many Features

We have pretty much refactor the code of “Hello World”, and we can already see the pattern of supporting multiple features. But, it’s not completed yet. Because, the entry point type Query is still in helloworld’s schema and resolver.

We will move type Query to a directory named _root. So, whenever we need to add new features, we will register it here at first.

_root

Let’s create some new directories:

  • <project-root>/graphql/_root
  • <project-root>/graphql/_root/queries

Then, add some empty files:

  • <project-root>/graphql/_root:
    • index.js
  • <project-root>/graphql/_root/queries:
    • index.js
    • resolvers.js
    • schemas.gql

For every index.js file, just copy them directly from the helloworld‘s directories. It’s the same.

We will add some codes in _root/queries/schemas.gql first:

module.exports = `
  type Query {
    helloworld: HelloWorld
  }
  
  schema {
    query: Query
  }
`;

Here is the “entry point”. It is important to first declare the type Query, and add the schema part. This way, the GraphQL server will know that what we will get from the top query is Query. Next, add the property helloworld inside type Query, which will be obtained from type HelloWorld.

Next, we modify _root/queries/resolvers.js to resolve the requested query result:

// Provide resolver functions for your schema fields
const Query = {
    helloworld: () => 'helloworld'
};

module.exports = { Query };

The resolver object must have the same as declared on schema. The property helloworld must be a function or an object. In this case, I created a dummy function which returns string. But, isn’t helloworld‘s type is HelloWorld? Yes, we will declare HelloWorld later in its own schema and resolver. Lastly, export Query as a module inside a bracket as an object.

Then, on /graphql/index.js, register _root‘s schemas and resolvers:

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

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

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

module.exports = {
    typeDefs,
    resolvers
};

First, import _root, then assign the root.schemas to typeDefs and the root.resolvers to resolvers.

Modify HelloWorld, again.

Yes, I know it’s the third time. I promise it’s the last time. Let’s make it quick then.

First, we modify helloworld/queries/schemas.gql:

module.exports = `
  type HelloWorld {
    hello: String
    world: String
  }
`;

In this file, we create the type for HelloWorld, the one we declared in Query before. It has the property hello and world, both are strings.

So, what is inside hello and world? We will resolve it in the helloworld/queries/resolvers.js file:

// Provide resolver functions for your schema fields
const HelloWorld = {
  hello: () => 'Hello!',
  world: () => 'World!'
};

module.exports = { HelloWorld };

Pretty similar to the _root resolver, but in this file it means that whenever a HelloWorld type is requested, it will return at least one of the properties hello or world. Then, we export the HelloWorld to be registered in the GraphQL server.

Test the code

Run npm start. And execute this query:

query {
  helloworld {
    hello
    world
  }
}

The result should be like in this screenshot if everything goes well:

Query Success!

Conclusion

Alright, it’s my favorite part of the post: the conclusion. So, at this point we should have learned about:

  • Quick introduction of GraphQL.
  • Setting up a GraphQL server.
  • Refactor a GraphQL server code to support many services later on.

And as promised, you can get the source code in GitHub here.

Actually, I wanted to write about the queries and mutations also in this post. The post became so long that I decided to make another post about it.

I hope this will be a help for you to create a GraphQL server. Lastly, if I missed something, let me know in the comments!

Next post in the series:
Part 2 – GraphQL Queries

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

― Sanhita Baruah

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 *