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 applicationgraphql
: the GraphQL npm package for Node.jsexpress-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 relationalposts
field, which returns an array of posts created by the userPost
: a type representing a publication created by a user in the application with its respective fieldsQuery
: in theQuery
type, we defined two pieces of information that can be queried from our GraphQL server as follows:users
: an array ofUsers
user
: a singleUser
with the specifiedid
in the arguments list
Mutation
: in theMutation
type, we defined theeditUser
mutation, which, given theid
of the user, can be called to edit a user and then update the information (name
andemail
)
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 casesimple-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 Adepoju is a LinkedIn Learning (Lynda.com) Author, Full-stack developer, technical writer, and tech content creator proficient in Web and Mobile technologies and DevOps with over 10 years experience developing scalable distributed applications. With over 40 articles written for CircleCI, Twilio, Auth0, and The New Stack blogs, and also on his personal Medium page, he loves to share his knowledge to as many developers as would benefit from it. You can also check out his video courses on Udemy.