Learn GraphQL: Build Contact Manager App – Part 2 of 3

This is a continuation from Part 1 of a three part series guide that will help you to learn about GraphQL, and also will help you to learn how it works with an application by building a realtime Contact Manager app with Angular, GraphQL,  Node.js/Express.js and MySQL.

Building the Contact Manager App

The Contact Manager app will be developed in Angular 5 which will consume the GraphQL API endpoint which we will be creating, and it will have the following basic features:

  • Add a new contact
  • Modify an existing contact
  • Delete a contact.
  • Sorting capability
  • Contact Search capability

Although this guide is written for MacOS environment and  Visual Studio Code IDE, the basic steps and information can be used for any environment.

Prerequisites

Before we get started with building Contact Manager app, you should have:

  • Latest Node.js installed.
  • MySQL database installed and running.
  • Basic knowledge of strong typing.
  • Internet connection (require for npm package installation)

Getting Started

Below are the step-by-step guide to build the contact manager app.

Step 1: Setting up the project

Create project folder, “contact-manager” and change directory to the newly created folder.

mkdir contact-manager
cd contact-manager

Create a folder, server which will contain all the GraphQL server related files. Initialize the package.json, when it is prompted for details press Enter till the end it generates the file.

mkdir server
npm init

Step 2: Setting up the Database

In phpMyAdmin right panel, select “SQL” tab as shown below.

MySQL Database creation
MySQL Database creation

Run below SQL query to create  extrajs_contact_management database.

CREATE DATABASE `extrajs_contact_management`;

Select the newly created database and run the below SQL to create the extrajs_contact table.

USE `extrajs_contact_management`;

CREATE TABLE IF NOT EXISTS `extrajs_contact` (
`id` bigint(20) unsigned NOT NULL,
`fullname` varchar(250) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '',
`email` varchar(100) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '',
`birthday` date NOT NULL DEFAULT '0000-00-00'
) ENGINE=InnoDB AUTO_INCREMENT=139 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci;

Let’s add primary key on the id column of the table.

USE `extrajs_contact_management`;

ALTER TABLE `extrajs_contact`
ADD PRIMARY KEY (`id`);

Let’s also add the auto-increment of the id column to generate the unique sequential number automatically.

USE `extrajs_contact_management`;

ALTER TABLE `extrajs_contact`
MODIFY `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT;

Our database is now ready to be used.

Step 3: Build GraphQL Server

Let’s start by installing the required packages – express, graphql and express-graphql . Save them as dependencies.

npm i express express-graphql graphql --save

Change directory to server and create the following required files for GraphQL server:

  • index.js – The entry point for our GraphQL endpoint.
  • db.js – Provides an easy way to connect to the database without the need to re-enter the credentials.
  • model.js – Provides our application to deal with anything related to database
  • schema.js – Schema definition for our GraphQL server.
cd server
touch index.js schema.js db.js model.js

Let us examine each of these files below:

index.js

Import the dependencies that we have installed above – express, express-graphql and schema.js. Setup the router for our API endpoint with handler passed to the express middleware expressGraphql which will process the request.  Also, setup to catch all other routes and pass them to Angular’s dist folder, which we will be setting up later in this guide. The server is configured to listen to port number 4000 and a log is added to know that the server is up and running on port 4000.

const express         = require('express');
const app             = express();
const expressGraphql  = require('express-graphql');
const schema          = require('./schema.js');
const path            = require('path');

// Define CORS
app.use(function(req, res, next) {
	res.header("Access-Control-Allow-Origin", "*");
	res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");

	// Handles preflight request
    if ('OPTIONS' == req.method) {
		res.send(200);
	  }

	next();
});
  
// Define the API route and the express middleware as the handler
app.use('/api', expressGraphql({
	schema: schema,
	graphiql: true
}));


// Define route for Angular
app.all('*', function(req, res, next) {
	// Path to angular dist folder
	res.sendFile(path.join(__dirname, '../client/dist/'+ req.path)); 
});


app.listen(4000, ()=>{
    console.log('Server is running  on port 4000..');
})

db.js

This is a wrapper which abstracts the database connection details and query invoking implementation. It has a dependency to NPM package mysql  which provides MySQL driver for node.js. Install the mysql dependency  as below.

npm i mysql --save

Import mysql  driver and set up the connection pool along with the required connection options.  Here, it is defined to connect to 127.0.0.1 host (a localhost) with root as the username and letmein as the password. Change this accordingly with your database and its user login details.

Export the initialized connection object along with executeQuery to make it available to be used in other modules.

const mysql    = require('mysql');

// Set up connection ppol
const pool     = mysql.createPool({
    host     : '127.0.0.1',
    user     : 'root',
    password : 'letmein',
    database : 'extrajs_contact_management'
});

// poolConnection and executeQuery are being wrapped with an object
// which is then exposed to make available for use in other module
module.exports =  {

    // Pool connection
    poolConnection: pool,

    // This is a wrapper function for invoking a query
    // and returns promise
    executeQuery: (sql, params) => {

        // rteurning promise
        return new Promise((resolve, reject) => {
    
            sql = mysql.format(sql, params);
    
            // Get the connection from the pool
            pool.getConnection(function(connErr, connection) {
                // Something happened! Not connected.
                if(connErr){
                    reject(connErr);
                }
    
                // Invoke query
                connection.query(sql, (queryErr, results)=>{
                    if(queryErr){
                        reject(queryErr);
                    }
        
                    // Resolve the promise with the resultsets
                    resolve(results);
                });
    
                // Release the connection back to the pool when done.
                connection.release();
            });
            
        });
    }
}

model.js

Import db.js in our model as we require to connect to the database, and also to build parameterized query and to invoke it.  Our model contains the following  methods:

  • getAll – Fetches all the contacts from the database.
  • getByName – Fetches a contact by fullname matches.
  • getById – Fetches by contact id.
  • create – Creates new contacts
  • update – Updates a particular contact details.
  • delete – Deletes a particular contact.
const mysql = require('mysql');
const db    = require('./db');


exports.Contacts = {
    getAll: (() => {
        return db.executeQuery("SELECT * FROM extrajs_contact");
    }),

    getByName: ((name) => {
        return db.executeQuery("SELECT * FROM extrajs_contact WHERE fullname LIKE ?", ['%'+ name +'%']);
    }),

    getById: ((id) => {
        return db.executeQuery("SELECT * FROM extrajs_contact WHERE id = ?", [id]);
    }),

    create: ((fieldList) => {
        let fieldValues = [fieldList.fullname, fieldList.email, fieldList.birthday];
        return db.executeQuery("INSERT INTO extrajs_contact SET fullname = ?, email = ?, birthday = ?", fieldValues);
    }),

    update: ((fieldList, whereList) => {
        let fieldItems  = [];
        let fieldValues = [];
        let conditions  = [];

        // Get fields
        for(field in fieldList){
            let fieldValue = fieldList[field];

            // If the field value is not defined go to next iteration
            if(fieldValue === undefined){
                continue;
            }

            // Add named placeholder by default, if disabledPlaceholder is true do not add placeholder
            field = disabledPlaceholder ? field : field +'=?';

            fieldItems.push(field);
            fieldValues.push(fieldValue);
        }

        // Get where conditions
        for(field in fieldList){
            let fieldValue = fieldList[field];

            // If the field value is not defined go to next iteration
            if(fieldValue === undefined){
                continue;
            }

            // Add named placeholder by default, if disabledPlaceholder is true do not add placeholder
            field = disabledPlaceholder ? field : field +'=?';

            conditions.push(field);
            fieldValues.push(fieldValue);
        }

        return db.executeQuery("UPDATE extrajs_contact SET "+ fieldItems.join(', ') +" WHERE "+ conditions.join(' AND '),  fieldValues);
    }),

    delete: ((id) => {
        return db.executeQuery("DELETE FROM extrajs_contact WHERE id = ?", [id]);
    })
}

schema.js

Create a GraphQL Object Type called ContactType with id, fullname, emailbirthday and phone as its fields. This defines the kind of object that can be fetched from our API and the fields which will be available.

Let’s define the query type and it’s fields – contact and contacts.

  • contact field accepts an argument name of type string, and resolves to contactType object by calling getByname method which fetches all the contacts records with fullname containing the name.
  • contacts field resolves to an array of contactType object by calling getAll method which fetches all the contacts records from the database.

Let’s also define the mutation type and it’s fields – createNewContactupdateContact and deleteContact

  • createNewContact field accepts four arguments – fullname of type string, email of type string, birthday of type string and phone of type string. This query resolves to newly contact id by calling create method which will create new contact with the provided arguments and then will return the newly created contact id.
  • updateContact field accepts five arguments – fullname of type string, email of type string, birthday of type string, id of type string and phone of type string. Except for the id all other arguments are optional. This query resolves to contactType object by calling update method which updates the record having the argument id with the provided other arguments.
// Imports the required types
const {
  GraphQLObjectType,
  GraphQLString,
  GraphQLID,
  GraphQLSchema,
  GraphQLList,
  GraphQLNonNull}   =  require('graphql');

// Imports the model
const model         = require('./model');

// Define the custom type for contact
const ContactType   = new GraphQLObjectType({
    name: 'Contact',
    fields: () => ({
     id       : {type: GraphQLID},
     fullname : {type: GraphQLString},
     email    : {type: GraphQLString},
     birthday : {type: GraphQLString},
     phone    : {type: GraphQLString}
    })
});


// Define Schema
module.exports = new GraphQLSchema({
    query: new GraphQLObjectType({
        name: 'RootQuery',
        description: 'Root Query',
        fields: () => ({
            //Query for a single contact by their name
            contact: {
                type: ContactType,
                args: {
                    name: {type: GraphQLString}
                },
                resolve(root, args){
                    return model.Contacts.getByName(args.name).then((results)=>{
                        // Return scalar value
                        return results.length ? results[0] : results;
                    }, (error)=>{
                        // Throw the error to GraphQL
                        throw error;
                    });;
                }
            },

            //Query for all contacts
            contacts: {
                type: new GraphQLList(ContactType),
                
                resolve(root, args){
                    return model.Contacts.getAll().then((results)=>{
                        // returns list of results
                        return results;
                    }, (error)=>{
                        // Throw the error to GraphQL
                        throw error;
                    });
                }
            }
        })
    }),

    mutation: new GraphQLObjectType({
        name: 'Mutation',
        fields: () => ({
            // Query for creating new contact
            createNewContact:{
                type: ContactType,
                args: {
                    fullname : {type: new GraphQLNonNull(GraphQLString)},
                    email    : {type: new GraphQLNonNull(GraphQLString)},
                    birthday : {type: new GraphQLNonNull(GraphQLString)},
                    phone    : {type: new GraphQLNonNull(GraphQLString)}
                },
                resolve(root, args){
                    return model.Contacts.create({
                        fullname : args.fullname, 
                        email    : args.email, 
                        birthday : args.birthday,
                        phone    : args.phone
                    }).then((results)=>{
                        return model.Contacts.getById(results.insertId).then((rows)=>{
                            // Returns a scalar value
                            return rows.length ? rows[0] : rows;
                        }, (error)=>{
                            throw error;
                        });
                    }, (error)=>{
                        throw error;
                    })
                }
            },
    
            updateContact:{
                type: ContactType,
                args: {
                    fullname : {type: GraphQLString},
                    email    : {type: GraphQLString},
                    birthday : {type: GraphQLString},
                    id       : {type: new GraphQLNonNull(GraphQLID)},
                    phone    : {type: GraphQLString}
                },
                resolve(root, args){
                    return model.Contacts.update({
                        fullname : args.fullname, 
                        email    : args.email, 
                        birthday : args.birthday,
                        phone    : args.phone
                    },
                    // Where condition 
                    {
                        id       : args.id
                    }).then((results)=>{

                        return model.Contacts.getById(args.id).then((rows)=>{
                            // Return scalar value
                            return rows.length ? rows[0] : rows;
                        }, (error)=>{
                            throw error;
                        });

                    }, (error)=>{
                        throw error;
                    });
                }
            },
    
            deleteContact:{
                type: new GraphQLList(ContactType),
                args: {
                    id: {type: new GraphQLNonNull(GraphQLID)}
                },
                resolve(root, args){
                    return model.Contacts.delete(args.id).then((results)=>{
                        return model.Contacts.getAll().then((rows)=>{
                            return rows;
                        }, (error)=>{
                            throw error;
                        });
                    }, (error)=>{
                        throw error;
                    });
                }
            }
        })
    })
});

Our GraphQL server is all set and ready for test. Let’s load some data by running below SQL query.

USE `extrajs_contact_management`;

INSERT INTO `extrajs_contact`
SET `fullname`= 'Kay Lawson',
`email` = '[email protected]',
`phone` = '(440)-176-7616',
`birthday` = '1970-5-5';

INSERT INTO `extrajs_contact`
SET `fullname`= 'Gene Dunn',
`email` = '[email protected]',
`phone` = '(870)-245-4093',
`birthday` = '1983-4-5';

INSERT INTO `extrajs_contact`
SET `fullname`= 'Elizabeth Mckinney',
`email` = '[email protected]',
`phone` = '(373)-865-2309',
`birthday` = '1977-7-4';

INSERT INTO `extrajs_contact`
SET `fullname`= 'Lester Lynch',
`email` = '[email protected]',
`phone` = '(494)-381-4370',
`birthday` = '1977-6-10';

Start the GraphQL server by running below command. If you get error trying to start the server, try changing the port number in index.js from 4000 to something else.

node index.js

To test the server open GraphiQL by navigating to the address – http://localhost:4000/api. Run below GraphQL query to fetch all contacts’fullname, phone and email. Execute the query by clicking the execute button, it should return the response in the result window.

query{
  contacts{
    fullname,
    phone,
    email
  }
}
GraphQL query test on GraphiQL
GraphQL query test on GraphiQL


In our next series, we will explore how to consume the  GraphQL endpoint we created above with an Angular app by building a simple Contact Manager app. Keep checking for the last part of the series which will be posted soon.

Mapu

I am a technology freak with passion for UI technology.

Leave a Reply