Build an App with GraphQL

Introduction

In my earlier article, we talked about GraphQL getting popularity replacing REST and did some comparison between those two. In this article, we will build a sample app for GraphQL and see how to create APIs using GraphQL server and client. GraphQL server can be built in NodeJS, Spring, and other supported frameworks. We are going to do that using the express-NodeJS module. Also, there are many clients available for GraphQL like Apollo, Relay, etc.. We will be focusing on Apollo client in this article. 
We are going to build an API for Class and Student resources. We will also define some relationships between class and students to see the power of GraphQL. In this example, we will show how a class can have multiple students and can be queried accordingly.

Pre-requisites

  • Familiarity with NodeJS  and installed on your system
  • Any IDE
  • Familiarity with ReactJS for client
  • A MongoDB database - You can use MongoDB atlas which gives a free account and sufficient for this article. 

Set up GraphQL Server

Let's first set up the server that will handle the grphql queries. The server is like an API that will implement the functionality for each graphql query by integrating with DB, third party services, etc...

Step 1 - Set up a Simple NodeJS Application

Create a folder named server. Use npm init command to create a default package.json file in it. Now install some of the basic modules required for this article -

npm install express express-graphql mongoose cors
You can also install nodemon module to do the hot deployment.
npm install nodemon -g

Step 2 - Setup Express GraphQL

     express is a widely used web application framework in NodeJS to create APIs. In this example, we will be using the express-graphql module to handle the graphql query requests. Add the below code in app.js file:
app.js
const express   =   require('express');
const app   =   express();
const graphqlHTTP   =   require('express-graphql');
const schema = require('./schema/schema');
app.use("/graphql", graphqlHTTP( {
    schema,
    graphiql: true
}));
app.listen(9000, () => {
    console.log("listening request on port 9000");
});
app.listen is used to host this application as an API server and listening to the port 9000. 
app.use("/graphql", graphqlHTTP... )  is used to route all the /graphql requests to express-graphql HTTP framework to handle GraphQL queries. 

express-graphql also provides a GUI which can be used to test the queries before giving it to Clients. By making graphiql: true, it enables that UI on /graphql path.

Remember to make graphiql:false in production. It is supposed to be used only during development.
The schema field is GraphQLSchema instance and mandatory for graphqlHTTP. We will discuss in Step 4 how to create this schema.

Step 3 - Set up MongoDB Connection

We have used mongoose client here to connect to the MongoDB. However, you can use MongoClient as well. Use the below code to create a connection. Add this code in app.js:
const mongoose  =   require('mongoose');
mongoose.connect('mongodb+srv://graphql:graphql@graphqldb-tochh.mongodb.net/test?retryWrites=true&w=majority', {useNewUrlParser: true, useUnifiedTopology: true});
mongoose.connection.once('open', ()=> {
    console.log('mongodb connected ');
    }
)

Step 4 -  Create MongoDB Schemas

Create Class and Student MongoDB schema so that we can use those to save and retrieve data from collections. An example schema for Class: 
const mongoose  =   require('mongoose');
const Schema    =   mongoose.Schema;
const classSchema   =   new Schema ( {
    grade: String,
    sections: String
});
module.exports  =   mongoose.model('Class', classSchema);

Step 5 -  Create GraphQL Schema 

Create a schema.js file and export it with GraphQLSchema object. There is another schema object called buildSchema as well. But GraphQLSchema is supposed to be better in terms of performance and flexibility.
schema.js
module.exports  =   new GraphQLSchema({
        query: RootQuery,
        mutation: Mutation
});
GraphQL schema has two options for manipulating the data. 
  1. Query - query is used to retrieving the data.
  2. Mutation - mutation is used to create and update the data records.

There is no direct method to delete the object in GraphQL. It needs to be handled by GraphQL Clients like updateQueries in Apollo. Logic needs to be written to remove the particular object from the list and update the object with updateQueries. There are side effects of this approach as all the cache queries will need to be refetched to sync them with the latest data.
Now, define the queries. A query is a GraphQLObjectType that has names and fields. Let's define the queries: 
  • retrieve class record based on the Id
  • retrieve student record based on the Id
  • retrieve all the classes
  • retrieve all the students
schema.js Collapse source
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
const RootQuery = new GraphQLObjectType({
    name: 'RootQueryType',
    fields: {
        class: {
            type: ClassType,
            args:  { id: { type: GraphQLString } },
            resolve(parent, args) {
               // return _.find(classes, { id: args.id });
               return Class.findById(args.id);
            }
        },
        student: {
            type: StudentType,
            args: { id: { type: GraphQLString } },
            resolve(parent, args) {
               // return _.find(students, {id: args.id});
               return Student.findById(args.id);
            }
        },
        classes:{
            type: new GraphQLList(ClassType),
            resolve(parent, args){
                //return classes;
                return Class.find();
            }
        },
        students:{
            type: new GraphQLList(StudentType),
            resolve(parent, args){
                //return classes;
                return Student.find();
            }
        }
    }
});
Each query is like a node in a graph. It will have type, args, and resolve method.
  • type - it can be a scalar like an integer, string, etc.. or a GraphQLObjectType.
  • args - these are inputs that are used to filter the query results.
  • resolve - resolve method defines how to calculate the value for the particular node/object. It can bring data from other services, DB or so. In this case, we are retrieving data from MongoDB collections.
An Example of ClassType that is mentioned in the first query:
schema.js
const ClassType =   new GraphQLObjectType({
    name: 'Class',
    fields: ( ) => ({
        id: { type: GraphQLString },
        grade: { type: GraphQLInt },
        sections: { type: GraphQLString },
        students: {
            type: new GraphQLList(StudentType),
            resolve(parent, args){
              //  return _.filter(students, {classid: parent.id});
              return Student.find({classid: parent.id});
            }
        }
    }) 
});
Now define mutation :
a mutation is also a GraphQLObjectType and has similar fields. The difference is in the implementation of the resolve method which will save the MongoDB object.
schema.js Expand source
So far, we have created the express-graphql server that can handle the queries and mutation for Class and Student objects. We can test that by using http://localhost:9090/graphql

Set up GraphQL Client

As the server is ready, now let's set up the client. There are many options for GraphQL Clients. We will be using Apollo Client for this example:

Step 1 - Set up React Project

Apollo Client can be used using many languages but React is the most common one. To set up a react app, we would need a create-react-app module. Create the app using the below command:
npm install create-react-app -g
create-react-app client

Step 2- Set up Apollo Client

Import apollo-boost and react-apollo module to create Apolloclient and ApolloProvider objects.  ApolloProivder is used to render the content under the React component.

app.js Collapse source
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import ApolloClient from 'apollo-boost';
import {ApolloProvider} from 'react-apollo';
import StudentList from './components/StudentList';
import AddStudent from './components/AddStudent';
//apollo client setup
const client =  new ApolloClient({
})
class App extends Component {
  render(){
    return (
      <ApolloProvider client={client}>
        <div id="main">
          <h1>Cloud Native Training Classes</h1>
          <h2>Student StudentList</h2>
          <StudentList></StudentList>
          <h2>Add New Students</h2>
          <AddStudent></AddStudent>
        </div>
      </ApolloProvider>
    );
  }
}

Step 3 - Write Queries

Import gql component from apollo-boost and write graphql compatible queries.

query.js Collapse source
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import {gql} from 'apollo-boost';
const getStudentQuery =   gql`
    {
        students{
            id
            name
            dresscode
        }
    }
`;
const getClassQuery =   gql`
    {
        classes{
            id
            grade
            sections
        }
    }
`;
const addStudentMutation = gql`
    mutation AddStudent($name: String!, $dresscode: String!, $classid: String!){
        addStudent(name: $name, dresscode: $dresscode, classid: $classid){
            name
            id
        }
    }
`;
export {getClassQuery, getStudentQuery, addStudentMutation};

Step 4 - Bind Query Results With React Component

The queries written using gql have to be bound with the React component so that data can be rendered on the UI. For that, we again will be using the react-apollo module.

studentlist.js Collapse source
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import {graphql} from 'react-apollo';
import {getStudentQuery}    from '../queries/query';
class StudentList extends Component {
    displayStudents(){
        var data    =   this.props.data;
        if(data.loading){
            return ( <div>Loading this...</div> );
        else{
            return data.students.map(student => {
                return( <li key="student.id">Name: {student.name} Dress Code: {student.dresscode}</li>
                );
            })
             
        }
    }
    render(){
        console.log(this.props);
        return (
            <div >
            <ui id='studentlist'>
                {this.displayStudents()}
            </ui>
            </div>
        );
    }
}
export default graphql(getStudentQuery)(StudentList);
graphql component will bind the getStudentQuery with StudentList and data will be loaded in props. Using props, we can retrieve each field and render it as a list. Similar code has to be written for AddStudent functionality and bind the mutation query with the react component.
Now, the client is ready and can start the application using the npm start command. 

Summary

To Summarize, GraphQL with express is very easy to implement and Apollo client is very much flexible to integrate with the GraphQL server. However, the feature for deleting a record is something missing at the server-side. You need to depend on the GraphQL Clients to provide the feature to handle that but there are side effects of using those solutions as most will try to remove the record from the list, clear all the existing queries' cache and update back to DB.
As usual, the code can be found over Bitbucket- graphql-express

No comments: