GraphQL continues to grow in popularity with each passing day, since its public release by Facebook in 2015. The technology enables front end clients to query exactly what they need from the backend, and is gaining wide recognition and adoption by reputable companies such as Pinterest, Coursera, Airbnb, and Facebook itself.

In this post, we will learn how to deploy a simple GraphQL server written in Node.js to Heroku via a continuous integration (CI) pipeline using CircleCI.

Prerequisites

To follow along with this post, you will need the following already set up:

  • Node.js installed on your system (you can confirm this by running the command node -v on your terminal to print out your version of Node.js installed)
  • Git installed on your system (you can confirm this by running the command git on your terminal; this should print out available git commands)
  • A Heroku account
  • A GitHub account
  • A CircleCI account

##Scaffolding the GraphQL server project To begin setting up a GraphQL server, create a new folder for the project by running the following command:

mkdir graphql-test-server

Now go into the root of the folder and run the following command to scaffold a new Node.js project:

npm init -y

The -y optional flag allows you to skip confirming the default answers to the questions for creating the package.json file and just accepts them.

We are going to create an ExpressJS Node.js application and build the GraphQL server. To do this, we need to install the following packages:

  • express: to create our ExpressJS Node.js application
  • graphql: the GraphQL npm package for Node.js
  • express-graphql: ExpressJS middleware for GraphQL

Install these packages by running the following command:

npm install --save express graphql express-graphql

Great!

We now have the required packages to start putting together our GraphQL server.

Defining the GraphQL schema

Create a folder named src at the root of your project by running the following command:

mkdir src

Within this folder, create a new file with the name schema.js and paste in the code below.


const { buildSchema } = require("graphql");

const schema = buildSchema(`
    type Query {
        users: [User!]!,
        user(id: Int!): User!
    }

    type Mutation {
        editUser(id: Int!, name: String!, email: String!): User!
    }

    type User {
        id: ID!
        name: String!
        email: String
        posts: [Post!]
    }

    type Post {
        id: ID!
        title: String!
        published: Boolean!
        link: String
    }
`);

module.exports = schema;


In the above code, we have created four types, consisting of two built-in types (Query and Mutation) and two custom types (User and Post).

Beginning with the custom types, we defined the following:

  • User: a type representing a user in the application with its respective fields and a relational posts field, which returns an array of posts created by the user
  • Post: a type representing a publication created by a user in the application with its respective fields
  • Query: in the Query type, we defined two pieces of information that can be queried from our GraphQL server as follows:
    • users: an array of Users
    • user: a single User with the specified id in the arguments list
  • Mutation: in the Mutation type, we defined the editUser mutation, which, given the id of the user, can be called to edit a user and then update the information (name and email)

The schema is created using the buildSchema of the graphql package and exported at the end of the file.

Mocking data

In order to have results for our queries, we need data. For this exercise, we will be using MongoDB.

However, since the goal of this post is to demonstrate deploying a simple GraphQL server, we won’t bother with setting up a MongoDB server. Instead, we will use a mocked version using the mongodb-memory-server. This package allows us to create and use an in-memory MongoDB database server.

To set this up, install the required packages by running the following command at the root of your project:

npm install --save mongodb mongodb-memory-server

This installation might take a while, depending on your download speed. MongoDB was at least 66 megabytes in size at the time of this writing.

Once the installation is complete, create a new file named data.js in the src folder. This file will export an array of hard-coded user data. Paste the following code below into the file.

const Users = [
  {
    id: 1,
    name: "Fikayo Adepoju",
    email: "fik4christ@yahoo.com",
    posts: [
      {
        id: 1,
        title: "Creating an Emoji Game with Vue, Auth0, and Google Vision API",
        published: true,
        link:
          "https://auth0.com/blog/creating-an-emoji-game-with-vue-auth0-and-google-vision-api/",
        author: 1
      },
      {
        id: 2,
        title: "Electron Tutorial: Building Modern Desktop Apps with Vue.js",
        published: true,
        link:
          "https://auth0.com/blog/electron-tutorial-building-modern-desktop-apps-with-vue-js/",
        author: 1
      },
      {
        id: 3,
        title: "State Management with Vuex: a Practical Tutorial",
        published: true,
        link:
          "https://auth0.com/blog/state-management-with-vuex-a-practical-tutorial/",
        author: 1
      }
    ]
  },
  {
    id: 2,
    name: "John Doe",
    email: "john@company.com",
    posts: [
      {
        id: 4,
        title: "Build a CI powered RESTful API with Laravel",
        published: true,
        link:
          "https://circleci.com/blog/build-a-ci-powered-restful-api-with-laravel/",
        author: 2
      },
      {
        id: 5,
        title: "Automate your Nuxt.js app deployment",
        published: true,
        link: "https://circleci.com/blog/automate-your-nuxt-js-app-deployment/",
        author: 2
      }
    ]
  },
  {
    id: 3,
    name: "Jane Paul",
    email: "jane@company.com",
    posts: []
  }
];

module.exports = {
  Users
};

In the code above, we have an array of user objects with their corresponding posts. This information will be used to seed our in-memory database once it is instantiated.

The next task is to set up our mock MongoDB database server. In the src folder, create a new file named database.js and insert the following code into the file.

const { MongoMemoryServer } = require("mongodb-memory-server");
const { MongoClient } = require("mongodb");
const data = require("./data");

let database = null;

async function startDatabase() {
  const mongo = new MongoMemoryServer();
  const mongoDBURL = await mongo.getConnectionString();
  const connection = await MongoClient.connect(mongoDBURL, {
    useNewUrlParser: true
  });

  //Seed Database
  if (!database) {
    database = connection.db();
    await database.collection("users").insertMany(data.Users);
  }

  return database;
}

module.exports = startDatabase;

In the code above, we export a startDatabase function. This function simply starts a new (mocked) MongoDB server instance and gets the connection string for the database instance. It then creates a new MongoDB client connection using the connection string.

With the connection to the database, we seed it with our user data exported from our data.js file. A check is made to see if the database reference is null before seeding it with data, in order to keep the data from being entered into the database each time the application starts up.

Finally, the function returns the database reference.

Defining our resolvers

Next up, we need to define resolvers for our GraphQL queries in order to return the appropriate data for each query operation. We will define resolvers for the users and user queries and the editUser mutation.

Create a new file in the src folder named resolvers.js and paste in the following code.

const resolvers = {
  users: async (_, context) => {
    const { db } = await context();
    return db
      .collection("users")
      .find()
      .toArray();
  },
  user: async ({ id }, context) => {
    const { db } = await context();
    return db.collection("users").findOne({ id });
  },
  //Mutation resolvers
  editUser: async ({ id, name, email }, context) => {
    const { db } = await context();

    return db
      .collection("users")
      .findOneAndUpdate(
        { id },
        { $set: { name, email } },
        { returnOriginal: false }
      )
      .then(resp => resp.value);
  } 
};

module.exports = resolvers;

As seen above, we now have resolvers for the three GraphQL requests available on our server. These resolvers fetch and update data using the database reference we created earlier by referencing it from the context object of the GraphQL server.

This reference will be added to the context object of our GraphQL server setup in the next section when we put everything together.

Setting up the GraphQL server

It is time to put every component we built together to set up our GraphQL server.

Before we begin, we need to install one more package. The graphql package comes with the GraphiQL application, which allows you to query your GraphQL endpoint, but let’s add a tool that is more robust. We will install the graphql-playground-middleware-express package to give us a fancier interface for querying our GraphQL endpoint.

Run the following code to install the package:

npm install --save graphql-playground-middleware-express

We will use this middleware to set up the GraphQL Playground for querying our server.

Let’s set up our server by creating a file named index.js at the root of the project and placing the following code in it.

const express = require("express");
const graphqlHTTP = require("express-graphql");
const schema = require("./src/schema");
const resolvers = require("./src/resolvers");
const startDatabase = require("./src/database");
const expressPlayground = require("graphql-playground-middleware-express").default;

// Create a context for holding contextual data 
const context = async () => {
  const db = await startDatabase();

  return { db };
};

const app = express();

app.use(
  "/graphql",
  graphqlHTTP({
    schema,
    rootValue: resolvers,
    context
  })
);

//Graphql Playground route
app.get("/playground", expressPlayground({ endpoint: "/graphql" }));

const port = process.env.PORT || "4000";

app.listen(port);

console.log(`🚀 Server ready at http://localhost:4000/graphql`);

In the file above, we start by importing all necessary modules. Then, we create our GraphQL context and return an object from it containing the reference to our MongoDB instance. Next, we create our ExpressJS application and set up the express-graphql middleware with GraphQL schema, resolvers, and context. We then set up the GraphQL Playground at the route /playground to load our GraphQL endpoint. Finally, we start our ExpressJS server to listen at port 4000 and print a message to the console.

Querying the GraphQL server

Now that we have our server set up, let’s take it for a spin. First, let’s create a start script in package.json to boot up our server.

Add the following script to the scripts section of the package.json file.

...

“scripts” : {
	...,
	“start” : “node index.js”
}

Run npm start to boot up the server.

Once the server is up and running, open up your browser and visit http://localhost:4000/playground to open up the playground. You will see a screen similar to the one below.

The playground automatically points to our GraphQL endpoint. From here we can write queries against our endpoint and get the results. Paste the query below into the query section of the playground and hit Play to run it:

{
  users {
    name
    email
    posts {
      title
      published
    }
  }
}

Running the above query will produce the result shown on the screen below.

As seen above, our GraphQL server is working fine and returning the expected data.

Deploying to Heroku with CircleCI

Our final task is to deploy our GraphQL server through a CI pipeline with CircleCI on the Heroku hosting platform.

We are going to take the following steps to deploy our GraphQL server:

  • Push the project to a GitHub repository
  • Create a new Heroku app and get the Heroku API key
  • Add a new project to our CircleCI account and connect it to the GitHub repo
  • Add our Heroku app name and API key as environment variables to our new project
  • Write our CircleCI configuration file for deployment to Heroku
  • Push the configuration to our GitHub repo to deploy to Heroku

Let’s begin. First, push the project to a GitHub repo.

Next, create a Heroku application as shown below. The name you enter is the name you will save as your Heroku app name on CircleCI.

You can get your Heroku API key by going to Account Settings and scrolling down to the API Key section.

The next step to deploying our application to Heroku is to connect the application on our GitHub repository to CircleCI.

Head to your CircleCI dashboard and add the project in the Add Project section.

Next to set up your project (in this case simple-graphql-node-server), click Set Up Project. This will bring you to a page similar to the one below.

Click Start building to begin setting up the project. This will immediately give us an error indicating that a CircleCI configuration file cannot be found in the project. This is understandable, as we are yet to include our pipeline configuration file. We will be doing that later.

The next step is to add our Heroku details as environment variables to the newly set up project. In order to push our project to Heroku from CircleCI, we need to configure an authenticated handshake between CircleCI and Heroku. This is done by creating two environment variables in the settings for your CircleCI project. The two environment variables are:

  • HEROKU_APP_NAME: This is the name of your Heroku application (in this case simple-graphql-node-server)
  • HEROKU_API_KEY: Your Heroku account API key. This can be found on the Account tab of your Heroku account under Account Settings.

To add these details, head over to your CircleCI dashboard and click Settings for your project. On the sidebar menu on the settings page, click Environment Variables under Build Settings.

On the Environment Variables page, create two variables named HEROKU_APP_NAME and HEROKU_API_KEY and add the respective values.

With these in place, our CircleCI configuration can use them to make authenticated deployments to the Heroku platform. Now, let’s write our CircleCI configuration to deploy our GraphQL server to Heroku.

Create a folder with the name .circleci at the root of your project and create a config.yml file inside it. Add the following configuration.

version: 2.1
orbs:
  heroku: circleci/heroku@0.0.10
workflows:
  heroku_deploy:
    jobs:
      - heroku/deploy-via-git

The code above uses CircleCI’s Heroku orb to perform a seamless deployment to Heroku. An orb is a reusable package of a YAML configuration that condenses repeated pieces of config into a single line of code. This one makes it very easy to deploy to Heroku without writing a sophisticated config yourself.

Save this file and push your changes to the GitHub repository. Then watch your CircleCI dashboard trigger a deployment after the successful push. You will see a successful deployment on your dashboard similar to the screen below.

Awesome!

Let’s now visit our Playground on the live application to confirm that our deployment is fine. Visit the link https://YOUR_HEROKU_APP_NAME.herokuapp.com/playground. For our exercise, this will be https://simple-graphql-node-server.herokuapp.com/playground as seen below.

Conclusion

In this article, we have been able to successfully put together a simple GraphQL server and automate its deployment using CircleCI. GraphQL is here to stay and is expected to gain more adoption, since its many benefits make it worthy of increased use.

Happy coding!


Fikayo is a fullstack developer and author with over a decade of experience developing web and mobile solutions. He is currently the Software Lead at Tech Specialist Consulting and develops courses for Packt and Udemy. He has a strong passion for teaching and hopes to become a full-time author.