How to setup a powerful API with GraphQL, Koa and MongoDB

How to setup a powerful API with GraphQL, Koa and MongoDB

Building API's is super fun! Especially when you can leverage modern technologies such as Koa, GraphQL and MongoDB. Koa is a Node ...

Building API's is super fun! Especially when you can leverage modern technologies such as Koa, GraphQL and MongoDB. Koa is a Node ...

Welcome to our part III series where we set up a powerful API. So far, we have achieved basic CRUD functionality.

As our app grows, so does our mutation count. In order to have as clean codebase as we can, we should extract the mutations to dedicated files. This way we can assure our code is modular and separated into maintainable chunks.

Let’s create a folder graphql/mutations and inside the folder create addGadget.js, updateGadget, and removeGadget files…

We simply place the mutation objects into the files and export them.

Moving addGadget mutation to separate file

graphql/mutations/addGadget.js

const { GraphQLObjectType, GraphQLString } = require('graphql');
const gadgetGraphQLType =  require('./../gadgetType');
const Gadget = require('./../../models/gadget');

module.exports = {
  type: gadgetGraphQLType,
  args: {
    name: { type: GraphQLString },
    release_date: { type: GraphQLString },
    by_company: { type: GraphQLString },
    price: { type: GraphQLString }
  },
  resolve(parent, args) {
    const newGadget = new Gadget({
      name: args.name,
      release_date: args.release_date,
      by_company: args.by_company,
      price: args.price,
    })

    return newGadget.save();
  }
};

Update mutation;

graphql/mutations/updateGadget.js

const { GraphQLObjectType, GraphQLString } = require('graphql');
const gadgetGraphQLType =  require('./../gadgetType');
const Gadget = require('./../../models/gadget');

module.exports = {
  type: gadgetGraphQLType,
  args: {
    id: { type: GraphQLString },
    name: { type: GraphQLString },
    release_date: { type: GraphQLString },
    by_company: { type: GraphQLString },
    price: { type: GraphQLString }
  },
  resolve(parent, args) {
    return Gadget.findById(args.id)
      .then(gadget => {
        gadget.name = args.name
        gadget.release_date = args.release_date,
        gadget.by_company = args.by_company,
        gadget.price = args.price

        return gadget.save()

      })
      .then(updatedGadget => updatedGadget)
      .catch(err => console.log(err))
  }
};

And finally the delete mutation.

graphql/mutations/removeGadget.js

const { GraphQLObjectType, GraphQLString } = require('graphql');
const gadgetGraphQLType =  require('./../gadgetType');
const Gadget = require('./../../models/gadget');

module.exports = {
  type: gadgetGraphQLType,
  args: {
    id: { type: GraphQLString }
  },
  resolve(parent, args) {
    return Gadget.findOneAndDelete(args.id).exec()
      .then(gadget => gadget.remove())
      .then(deletedGadget => deletedGadget)
      .catch(err => console.log(err))
  }
};

After we have them in separate files, we’re ready to change our graphql/mutations.js file.

const { GraphQLObjectType, GraphQLString } = require('graphql');

const addGadget = require('./mutations/addGadget');
const updateGadget = require('./mutations/updateGadget');
const removeGadget = require('./mutations/removeGadget');

const Mutation = new GraphQLObjectType({
  name: 'Mutation',
  fields: {
    addGadget,
    updateGadget,
    removeGadget,
  }
})

module.exports = Mutation;

Lovely – much cleaner!

Here’s how our projet should look like now;

Our GraphQL API is getting into great shape. So far we have a dedicated folder for mutations. Let’s do the same for queries.

Create a folder for the queries – graphql/queries and place the rootQuery.js file there.

Inside the rootQuery.js file we place all the queries which just fetch data. This way we have separate folders for queries and mutations. The difference between queries and mutations is simple–queries just read data from the database, mutations change the state of our database.

graphql/queries/rootQuery.js

const { GraphQLObjectType, GraphQLString, GraphQLList } =  require('graphql');

const gadgetGraphQLType =  require('./../gadgetType');
const Gadget = require('../../models/gadget');
const queryAllGadgets = require('./queryAllGadgets')

const RootQuery = new GraphQLObjectType({
  name: 'RootQueryType',
  fields: {
    gadget: {
      type: gadgetGraphQLType,
      args: { id: { type: GraphQLString }},
      resolve(parent, args) {
        return Gadget.findById(args.id)
      }
    },

  }
})

module.exports = RootQuery;

Import the rootQuery to our schema.js

const { GraphQLSchema } = require('graphql');

const RootQuery = require('./queries/rootQuery');
const Mutations = require('./mutations');


module.exports = new GraphQLSchema({
  query: RootQuery,
  mutation: Mutations
});

Now inside the rootQuery we specify all the queries. Create a file for fetching all gadgets and fetching gadgets by id.

  • graphql/queries/queryAllGadgets.js
  • graphql/queries/queryGadgetById.js

Place the following logic inside graphql/queries/queryGadgetById.js

const { GraphQLString } =  require('graphql');
const gadgetGraphQLType = require('./../gadgetType');
const Gadget = require('../../models/gadget');

module.exports = {
  type: gadgetGraphQLType,
  args: { id: { type: GraphQLString }},
  resolve(parent, args) {
    return Gadget.findById(args.id)
  }
},

And for fetching all gadgets – graphql/queries/queryAllGadgets.js

const { GraphQLList } =  require('graphql');
const gadgetGraphQLType = require('./../gadgetType');
const Gadget = require('../../models/gadget');

module.exports = {
  type: new GraphQLList(gadgetGraphQLType),
  args: {},
  resolve() {
    return Gadget.find({})
  }
}

Notice the new type GraphQLList – this is used if we want to return a list of objects–all gadgets in this case.

Now we have to import the queries to our rootQuery

const { GraphQLObjectType } =  require('graphql');

const Gadget = require('../../models/gadget');
const queryAllGadgets = require('./queryAllGadgets')
const queryGadgetById = require('./queryGadgetById');

const RootQuery = new GraphQLObjectType({
  name: 'RootQueryType',
  fields: {
    queryGadgetById,
    queryAllGadgets,
  }
})

module.exports = RootQuery;

Voila!

Now, Let’s query all the gadgets from our database.

Here’s the GraphQL query;

{
  queryAllGadgets {
    name,
    id,
    price,
    release_date,
    by_company
  }
}

Our project structure should look like the following;

Now we have dedicated folders for querying and mutating.

Types Folder

Ideally, we want to have a folder for GraphQL types as well. So let’s move our gadgetType.js there.

graphql/types/gadgetType.js

Don’t forget to update the paths where we use the gadgetType (mutations and queries)

const gadgetGraphQLType = require('./../types/gadgetType');

Testing our queries

Before moving to more advanced concepts, I’d like to show you a tool which lets us query graphql queries, if you ever used postman or insomnia you feel at home.

Head over to insomnia.rest and download the client. I use Insomnia since it’s free, has great functionality out of the box, and is open sourced.

Disclaimer: I’m not affiliated with Insomnia– I just like the tool.

Open the tool, create a new request called queryAllGadgets. At the top place our localhost address (http://localhost:9000/graphql) and specify that the body is graphQL.

And finally, place the graphQL query inside the body request.

Voila! We can query our graphQL in a headless manner now, just like we do with REST. If REST can do it, so can GraphQL.

Finishing touches

Adding all our queries to insomnia and hitting save. This way we have quick access to our queries.

Save the project. We’ll need it for later

Beautiful! We have successfully refactored our graphQL to a scalable version with headless testing.

Here’s the source code for the chapter.

Checkout out part 4 where we deploy our GraphQL API and make it secure

Thanks for reading!

Learn More

GraphQL with React: The Complete Developers Guide

GraphQL with Angular & Apollo - The Full-stack Guide

GraphQL: Learning GraphQL with Node.Js

Complete guide to building a GraphQL API

GraphQL: Introduction to GraphQL for beginners

The Complete Developers Guide to MongoDB

Master MongoDB, the NOSQL leader with Clarity and Confidence

MongoDB, NoSQL & Node: Mongoose, Azure & Database Management

Build a ChatApp with: (Nodejs,Socketio, Expressjs ,MongoDB)

GraphQL: Learning GraphQL with Node.Js

Building A GraphQL API With Nodejs And MongoDB

Building A GraphQL API With Nodejs And MongoDB

In this tutorial, you’ll build and deploy a GraphQL server with Node.js that can query and mutate data from a MongoDB database that is running on Ubuntu 18.04.

While REST APIs are amongst the most popular when it comes to client consumption, they are not the only way to consume data and they aren’t always the best way. For example, having to deal with many endpoints or endpoints that return massive amounts of data that you don’t need are common. This is where GraphQL comes in.

With GraphQL you can query your API in the same sense that you would query a database. You write a query, define the data you want returned, and you get what you requested. Nothing more, nothing less. I actually had the opportunity to interview the co-creator of GraphQL on my podcast in an episode titled, GraphQL for API Development, and in that episode we discuss GraphQL at a high level.

You might remember that I wrote a tutorial titled, Getting Started with GraphQL Development Using Node.js which focused on mock data and no database. This time around we’re going to take a look at including MongoDB as our NoSQL data layer.

A few assumptions are going to be made going forward. I’m going to assume that you already have access to a MongoDB instance. If you don’t and need help getting MongoDB setup, you might check out my tutorial titled, Getting Started with MongoDB as a Docker Container Deployment. The other assumption is that you have Node.js installed and configured. While this tutorial will be a working example, if you want to get into more depth with GraphQL, I suggest you check out my eBook and video course titled Web Services for the JavaScript Developer.

Creating a New Node.js Project for GraphQL Development

The first step in this tutorial will be to create a new project with the dependencies and boilerplate GraphQL code. From the command line, execute the following commands:

npm init -y
npm install express express-graphql graphql mongoose --save

The above commands will create a new package.json file and install our dependencies. The backbone of this project will use Express.js, which is also commonly used for RESTful APIs. However, we’ll be processing GraphQL requests and connecting them to MongoDB with the Mongoose ODM.

The next step is to create an app.js file and include the following boilerplate JavaScript code:

const Express = require("express");
const ExpressGraphQL = require("express-graphql");
const Mongoose = require("mongoose");
const {
    GraphQLID,
    GraphQLString,
    GraphQLList,
    GraphQLNonNull,
    GraphQLObjectType,
    GraphQLSchema
} = require("graphql");

var app = Express();

Mongoose.connect("mongodb://localhost/thepolyglotdeveloper");

const PersonModel = Mongoose.model("person", {
firstname: String,
lastname: String
});

const PersonType = new GraphQLObjectType({
name: "Person",
fields: {
id: { type: GraphQLID },
firstname: { type: GraphQLString },
lastname: { type: GraphQLString }
}
});

const schema = new GraphQLSchema({});

app.use("/graphql", ExpressGraphQL({
schema: schema,
graphiql: true
}));

app.listen(3000, () => {
console.log("Listening at :3000...");
});

In the above code we are importing our dependencies, initializing Express.js, and connecting to our MongoDB instance with Mongoose. We’re planning to use a very simple Mongoose model which will also be seen in our GraphQL model.

Rather than reiterating the content that was discussed in the previous tutorial, we’re going to focus primarily on the schema in our project which will contain queries and mutations.

Designing GraphQL Queries for Retrieving MongoDB NoSQL Data

When it comes to our schema, there will be queries for retrieving data and mutations for creating, updating, or deleting data. We’re going to start by designing our queries which will retrieve data from MongoDB.

Take a look at the following queries:

const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: "Query",
fields: {
people: {
type: GraphQLList(PersonType),
resolve: (root, args, context, info) => {
return PersonModel.find().exec();
}
},
person: {
type: PersonType,
args: {
id: { type: GraphQLNonNull(GraphQLID) }
},
resolve: (root, args, context, info) => {
return PersonModel.findById(args.id).exec();
}
}
}
})
});

In the above code we have a people query as well as a person query. One will retrieve multiple documents and the other will retrieve a single document. When we wish to query for multiple documents, we specify we want to return a GraphQLList of the PersonType that we had created. This PersonType maps to our document model. We can simply do a find for all documents within our people MongoDB collection.

The person query is similar, but not quite the same. In the person query we accept an argument which must be present. With that argument we can use the findById function and return the result.

We didn’t need to specify the document properties because they are already conveniently mapped to our model because of our naming conventions. If we wanted to use different names, we could specify the properties in the find operation.

If we wanted to query for our data from a client facing application, we could run something like this:

{
people {
id,
firstname,
lastname
}
person(id: "123") {
firstname
}
}

You can try to run this query by navigating to http://localhost:3000/graphql in your web browser because we have GraphiQL enabled for troubleshooting.

Designing GraphQL Mutations for Creating, Changing, or Deleting MongoDB Documents

Now that we can query for documents, we probably want a way to create documents. Mutations are designed nearly the same as queries, but they are executed differently in the client facing application.

Within your schema we can modify it to the following:

const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: "Query",
fields: {
people: {
type: GraphQLList(PersonType),
resolve: (root, args, context, info) => {
return PersonModel.find().exec();
}
},
person: {
type: PersonType,
args: {
id: { type: GraphQLNonNull(GraphQLID) }
},
resolve: (root, args, context, info) => {
return PersonModel.findById(args.id).exec();
}
}
}
}),
mutation: new GraphQLObjectType({
name: "Mutation",
fields: {
person: {
type: PersonType,
args: {
firstname: { type: GraphQLNonNull(GraphQLString) },
lastname: { type: GraphQLNonNull(GraphQLString) }
},
resolve: (root, args, context, info) => {
var person = new PersonModel(args);
return person.save();
}
}
}
})
});

Notice that now we have a mutation and not just a query. For the mutation, we are requiring two arguments to exist and we are using them in the resolve function. Using the arguments, without further data validation, we create a new model instance and save it to the database. The results are returned to the client that executed the mutation.

To try this mutation, the following could be executed:

mutation CreatePerson($firstname: String!, $lastname: String!) {
person(firstname: $firstname, lastname: $lastname) {
id,
firstname,
lastname
}
}

The above code would make sense in GraphiQL. The variables would be populated with another part of the GraphiQL application. Various frameworks will have different expectations when it comes to executing mutations, but the same idea applies.

Conclusion

You just saw how to use MongoDB as the NoSQL database in your GraphQL web application. GraphQL can be used in combination with REST or as an alternative depending on your business needs. If you’d like to know how to create a RESTful application with MongoDB, you might want to check out my previous tutorial titled, Building a REST API with MongoDB, Mongoose, and Node.js.

This example was quite simple and a lot more can be accomplished with GraphQL.

Thanks For Visiting, Keep Visiting. If you liked this post, share it with all of your programming buddies!

How to setup a powerful API with GraphQL, Koa and MongoDB

How to setup a powerful API with GraphQL, Koa and MongoDB

Building API’s is super fun! Especially when you can leverage modern technologies such as Koa, GraphQL and MongoDB.

Building API’s is super fun! Especially when you can leverage modern technologies such as Koa, GraphQL and MongoDB.

Koa is a Node framework, just like Express is a Node framework. We’ll replace Express with Koa since Koa uses async/await syntax over callbacks.

Koa Github repository

Express Github repository

Getting started

The prerequisites for building our API are the following;

  • Node installed
  • Text Editor; I pick Visual Studio Code
  • Terminal
  • Browser

If you have everything you need, please proceed - if not, please install them.

Open your terminal and create a node project, like so;

So far we created our project folder, initialized a fresh Node project. Now we have the NPM packages available which we can use to install Koa, Mongo, and GraphQL.

Let’s install koa with NPM.

npm i koa

Starting a new Koa server is very simple. All we need is a server.js file and with the contents;

const Koa = require('koa');

const app = new Koa();

app.listen(9000);

app.on('error', err => {
  log.error('server error', err)
});

Starting the project with Node

GraphQL

We need two packages to setup GraphQL with Koa. koa-mount and koa-graphql.

npm i koa-mount koa-graphql

GraphQL requires we pass our initial schema to the GraphQL server. Let’s create one.

We will place the graphQL schema at graphql/schema.js

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

const schema = buildSchema(`
  type Query {
    hello: String
  }
`);

module.exports = schema;

We pass our initial Query to the buildSchema function

Note: Notice how the argument for the buildSchema is a template literal. I encourage you take a look at this article if this is unfamiliar.

And now we can pass the initial schema to our graphql server.

app.use(mount('/graphql', graphqlHTTP({
  schema: schema,
  graphiql: true
})))

Don’t forget to import koa-mount, koa-graphql, and finally the schema.js.

const mount = require('koa-mount');
const graphqlHTTP = require('koa-graphql');
const schema = require('./graphql/schema');

If we head over to localhost:9000/graphql;

Voila! Initial setup is done. It’s not very useful yet. Ideally, we would like to query GraphQL to save data to our mongodb and read from there.

Setting up MongoDB

In order to read and write with GraphQL, we need a place to read from. This is where Mongo will come in handy. We’ll save and read our data from there.

To make things simpler, we’re gonna use a cloud instance for Mongo. Head over to mlab.com and create a user and a mongo database.

Creating mongoDB

Once you created the database–you’ll need a user for the database.

Creating the MongoDB user

Now you can use the mongoDB paired with Mongoose. The remote url for your database will be;

mongodb://:@ds213615.mlab.com:13615/koa-graphql

Installing mongose

npm i mongoose

Creating database.js file

We create a dedicated file for databse connection.

const mongoose = require('mongoose');

const initDB = () => {

  mongoose.connect(
    'mongodb://indrek:[email protected]:13615/koa-graphql',
    { useNewUrlParser: true }
  );

  mongoose.connection.once('open', () => {
    console.log('connected to database');
  });

}

module.exports = initDB;

Note: Make sure you use the username and credentials for your database.

This block of code will try to connect to the remote mongodb. We need to call it somewhere now.

Open server.js and require and call the method.

const initDB = require('./database');

initDB();

If we did everything correct, our console should tell us we connected succesfully.

Bravo!

Notice how annoying it is to constantly refresh the server. Let’s solve this with a package called pm2

PM2 is a production process manager for Node.js applications with a built-in load balancer. It allows you to keep applications alive forever, to reload them without downtime and to facilitate common system admin tasks.

npm install pm2 -g

Add a script called start to our package.json

"scripts": {
  "start": "pm2 start server.js"
},

Very lovely for pm2 to run in the background, which frees up our terminal. If you ever want to stop the process, just run pm2 kill. Now we don’t have to restart our server all the time, pm2 does it automatically.

Note: pm2 logs returns the console log statements to the terminal.

MongoDB models

If you ever worked Mongo, you’re aware that mongoDB let’s us creates Models for our data. This is a neat way for us to structure how our data will look like.

Create a folder models and inside a file gadgets.js

const mongoose = require('mongoose');
const Schema = mongoose.Schema;

/*
  notice there is no ID. That's because Mongoose will assign
  an ID by default to all schemas
*/

const GadgetSchema = new Schema({
  name: String,
  release_date: Date,
  by_company: String,
  price: Number,
});

module.exports = mongoose.model('Gadget', GadgetSchema);

Note: There is no ID field in our schema. That’s because Mongoose will assign

an ID by default to all schemas

Great. Let’s also add a collection and some dummy data. The collection name has to map our gadget name in plural. gadgets in this case.

After creating the collection, insert the document in JSON format like so;

That’s all for Mongo. Let’s fetch the data with GraphQL.

GraphQL Queries

GraphQL requires us to create types as well. Think of it like instructions for computers.

graphql/gadgetType.js

const graphql = require('graphql');

const { GraphQLObjectType, GraphQLString } = graphql;

const GadgetType = new GraphQLObjectType({
  name: 'Gadget',
  fields: () => ({

  })
});

module.exports = GadgetType;

Notice we created a graphql type. Inside the fields we can specify the properties of the given type.

const graphql = require('graphql');

const { GraphQLObjectType, GraphQLObjectType } = graphql;

const GadgetType = new GraphQLObjectType({
  name: 'Gadget',
  fields: () => ({
    id: { type: GraphQLString },
    name: { type: GraphQLString },
    release_date: { type: GraphQLString },
    by_company: { type: GraphQLString },
    price: { type: GraphQLString }
  })
});

module.exports = GadgetType;

Notice the GraphQLObjectType and GraphQLObjectType types we deconstruct from graphQL. These are the primitive types for graphQL.

Creating the graphQL types also grants type-hinting which we’ll use when creating our queries.

Last thing we need to do is refactor our schema.js. We want to query a gadget by id.

import the Gadget model,gadgetGraphQLType graphql type and GraphQLSchema, GraphQLObjectType, GraphQLString from graphQL to schema.js.

const { GraphQLSchema, GraphQLObjectType, GraphQLString} = require('graphql');
const gadgetGraphQLType =  require('./gadgetType');
const Gadget = require('../models/gadget');

Next we need a root query. Every graphql query starts with curly brackets {} – this the root query.

const RootQuery = new GraphQLObjectType({
  name: 'RootQueryType',
  fields: {

  }
})

Voila! Inside the fields we can specify the gadget query.

const RootQuery = new GraphQLObjectType({
  name: 'RootQueryType',
  fields: {
    gadget: {
      type: gadgetGraphQLType,
      args: { id: { type: GraphQLString }},
      resolve(parent, args) {
        return Gadget.findById(args.id)
      }
    }
  }
})

Notice the three properties inside the gadget query.

  • Node installed
  • Text Editor; I pick Visual Studio Code
  • Terminal
  • Browser

And finally export it.

module.exports = new GraphQLSchema({
  query: RootQuery
});

The file schema.js file should look like this;

const { GraphQLSchema, GraphQLObjectType, GraphQLString} = require('graphql');
const gadgetGraphQLType =  require('./gadgetType');
const Gadget = require('../models/gadget');

const RootQuery = new GraphQLObjectType({
  name: 'RootQueryType',
  fields: {
    gadget: {
      type: gadgetGraphQLType,
      args: { id: { type: GraphQLString }},
      resolve(parent, args) {
        return Gadget.findById(args.id)
      }
    }
  }
})

module.exports = new GraphQLSchema({
  query: RootQuery
});

Now head over to http://localhost:9000/graphql and make the query.

{
	gadget(id: "5c4e188efb6fc05326ad9264") {
		name
    price
    by_company
    release_date
    id
  }
}

This it what we should end up with!

Thanks for reading! ❤ The source code can be found here.

Building a GraphQL API with Node, Express and MongoDB

Building a GraphQL API with Node, Express and MongoDB

Create a GraphQL API With Node.js, Mongoose, and Express .This quick-and-dirty tutorial is just the beginning of all the fun you can have using GraphQL to make your development stronger, cleaner, and more efficient.

GraphQL is a technology that helps developers build robust software more quickly. The ability to request all of the information you need in a single request is a game changer.

It has simplified the back-end development of APIs for consumption by mobile and web applications that would normally rely on RESTful APIs. A normal RESTful API may have several end points for various entities (e.g., users, submissions, etc.); with GraphQL, you can get all of this information in a single go using GraphQL’s query language, also known as GQL.

In this tutorial, I’ll walk you through how to build a GraphQL API with graphql-compose-mongoose, as well as a few other tools. And, of course, everything will be to ES6 spec using Node.js. If this sounds like an exciting adventure, read on.

Getting Started

To get started, we’ll need to double-check you have a few prerequisites to ensure both that you understand the technology and that you can complete the tutorial in full.

Prerequisites

  • Node.js (Latest 13.x or above)
  • Yarn (brew install yarn on macOS)
  • An understanding of JavaScript and the ES6 spec
  • An account with MongoDB Atlas or a local instance of MongoDB running
Directory Structure

To start, create a new directory.

You can name your directory whatever you would like; for this tutorial, we’re going to create a to-do application, so I called mine todo.

mkdir todo && cd todo

mkdir.sh

Next, let’s go ahead and generate our package.json file using Yarn. We’ll add modules, as necessary, as we continue to move forward.

yarn init

init.js

Note: Answer the questions as prompted. Nothing necessarily required here — just whatever you’d like to set as your defaults.

Because we are using ES6, we’ll need to transpile all code from ES6 to vanilla JavaScript. To do so, let’s go ahead and create a src directory. Note that we’ll also need to set up the required structure within the src. The script below will accomplish the following:

  • Make a src directory
  • Move into the src directory
  • Generate schema, models, scripts, and utils directories
mkdir src && cd src && mkdir models schema scripts utils

src.sh

Lastly, we’ll create an index.js file, which will allow us to import our dependent files and directories:

touch index.js

index.sh

Inside of index.js, place the following contents, and save:

import dotenv from 'dotenv';
import express from 'express';
import { ApolloServer } from 'apollo-server-express';

import mongoose from 'mongoose';

import './utils/db';
import schema from './schema';

dotenv.config();

const app = express();

const server = new ApolloServer({
    schema,
    cors: true,
    playground: process.env.NODE_ENV === 'development' ? true : false,
    introspection: true,
    tracing: true,
    path: '/',
});

server.applyMiddleware({
    app,
    path: '/',
    cors: true,
    onHealthCheck: () =>
        // eslint-disable-next-line no-undef
        new Promise((resolve, reject) => {
            if (mongoose.connection.readyState > 0) {
                resolve();
            } else {
                reject();
            }
        }),
});

app.listen({ port: process.env.PORT }, () => {
    console.log(`🚀 Server listening on port ${process.env.PORT}`);
    console.log(`😷 Health checks available at ${process.env.HEALTH_ENDPOINT}`);
});

index.js

Package File

Now that we have the base files in place, let’s go ahead and add the required production packages to our package.json file using Yarn, like so:

yarn add @babel/cli @babel/core @babel/node @babel/preset-env apollo-engine apollo-server-express body-parser cors dotenv express graphql graphql-compose graphql-compose-connection graphql-compose-mongoose graphql-middleware graphql-tools mongoose mongoose-bcrypt mongoose-timestamp

package.sh

And for development packages, add the following:

yarn add --dev babel-eslint babel-loader babel-preset-env eslint eslint-plugin-babel eslint-plugin-import eslint-plugin-node eslint-plugin-promise fs-extra nodemon prettier

package.sh

Now that we have the necessary packages installed, we can modify our package.json file to allow for additional functionality.

Let’s modify it to add scripts and hooks; once we’ve done that, your package.json file should look much like this:

Scripts

The below will allow us to run scripts via Yarn (e.g., yarn <INSERT SCRIPT HERE>). For example, we can lint our code using yarn lint, and it’ll perform ESLint and Prettier operations on our files.

"scripts": {
    "build": "babel src --out-dir dist",
    "start": "node dist/index.js",
    "dev": "nodemon --exec npx babel-node src/index.js",
    "prettier": "prettier --config ./.prettierrc --write \"**/*.js\"",
    "pretest": "eslint --ignore-path .gitignore .",
    "postinstall": "rm -rf dist && yarn run build",
    "lint": "yarn prettier --write --check --config ./.prettierrc \"**/*.js\" && eslint --fix ./src",
    "release": "release-it patch --no-npm.publish"
}

scripts.json

Similar to above, we’ll add a Husky script that will trigger on the precommit event, effectively running yarn lint for us prior to committing code.

This is an excellent practice for maintaining quality, clean code:

"husky": {
    "hooks": {
        "pre-commit": "yarn lint"
    }
}

husky.json

That’s all for scripts. Let’s continue on.

Configuring Babel, Prettier, and ESLint

We’ve taken the necessary steps to install the correct packages for Babel, Prettier, and ESLint.

Now, it’s time to add the configuration files to the root of your project. Move the root, and add the following files:

.babelrc


{
    "presets": [
        [
            "env",
            {
                "targets": {
                    "node": "current"
                }
            }
        ]
    ]
}

babel.js

prettierrc.json

{
    "trailingComma": "es5",
    "tabWidth": 4,
    "semi": true,
    "singleQuote": true
}

.babelrc.json

.eslintrc.json

{
    "plugins": ["babel"],
    "extends": ["eslint:recommended"],
    "rules": {
        "no-console": 0,
        "no-mixed-spaces-and-tabs": 1,
        "comma-dangle": 0,
        "no-unused-vars": 1,
        "eqeqeq": [2, "smart"],
        "no-useless-concat": 2,
        "default-case": 2,
        "no-self-compare": 2,
        "prefer-const": 2,
        "object-shorthand": 1,
        "array-callback-return": 2,
        "valid-typeof": 2,
        "arrow-body-style": 2,
        "require-await": 2,
        "react/prop-types": 0,
        "no-var": 2,
        "linebreak-style": [2, "unix"],
        "semi": [1, "always"]
    },
    "env": {
        "node": true
    },
    "parser": "babel-eslint",
    "parserOptions": {
        "sourceType": "module",
        "ecmaVersion": 2018,
        "ecmaFeatures": {
            "modules": true
        }
    }
}

.eslintrc.json

Perfect! We’re making progress.

Onto the next section.

Creating Our Models

The reason I enjoy working with graphql-compose-mongoose is that it allows me to use Mongoose models rather than writing GraphQL models by hand (which, by the way, can become quite cumbersome on a large application).

Head over to src/models, and create a new file named user.js. Inside this file, we’ll define all of the required characteristics that make up a user. This will be a small file, but feel free to add additional information to the user record if you wish (for example, a password using mongoose-bcrypt).

import mongoose, { Schema } from 'mongoose';
import timestamps from 'mongoose-timestamp';
import { composeWithMongoose } from 'graphql-compose-mongoose';

export const UserSchema = new Schema(
    {
        name: {
            type: String,
            trim: true,
            required: true,
        },
        email: {
            type: String,
            lowercase: true,
            trim: true,
            unique: true,
            required: true,
        },
    },
    {
        collection: 'users',
    }
);

UserSchema.plugin(timestamps);

UserSchema.index({ createdAt: 1, updatedAt: 1 });

export const User = mongoose.model('User', UserSchema);
export const UserTC = composeWithMongoose(User);

user.js

Next, let’s create a task.js file (given that this is, after all, a to-do GraphQL API):

import mongoose, { Schema } from 'mongoose';
import timestamps from 'mongoose-timestamp';
import { composeWithMongoose } from 'graphql-compose-mongoose';

export const TaskSchema = new Schema(
    {
        user: {
            type: Schema.Types.ObjectId,
            ref: 'User',
            required: true,
        },
        task: {
            type: String,
            trim: true,
            required: true,
        },
        description: {
            type: String,
            trim: true,
            required: true,
        },
    },
    {
        collection: 'tasks',
    }
);

TaskSchema.plugin(timestamps);

TaskSchema.index({ createdAt: 1, updatedAt: 1 });

export const Task = mongoose.model('Task', TaskSchema);
export const TaskTC = composeWithMongoose(Task);

task.js

We now have two models/schemas: UserSchema and TaskSchema.

A user is an individual entity, and a task always belongs to a user. From this, we will eventually be able to pull all tasks for a user in a single GraphQL call. Pretty cool, right?

Creating Our Schemas

Schemas are an interesting part of this implementation. They, essentially, allow us to define what calls can and cannot be made to the server.

Schemas are made up of queries and mutations, where queries allow you to fetch data, and mutations allow you to modify data. Let’s create our schemas for both the user and task model.

Inside of the schema directory, create a file called user.js. Then, drop the following contents into the file:

import { User, UserTC } from '../models/user';

const UserQuery = {
    userById: UserTC.getResolver('findById'),
    userByIds: UserTC.getResolver('findByIds'),
    userOne: UserTC.getResolver('findOne'),
    userMany: UserTC.getResolver('findMany'),
    userCount: UserTC.getResolver('count'),
    userConnection: UserTC.getResolver('connection'),
    userPagination: UserTC.getResolver('pagination'),
};

const UserMutation = {
    userCreateOne: UserTC.getResolver('createOne'),
    userCreateMany: UserTC.getResolver('createMany'),
    userUpdateById: UserTC.getResolver('updateById'),
    userUpdateOne: UserTC.getResolver('updateOne'),
    userUpdateMany: UserTC.getResolver('updateMany'),
    userRemoveById: UserTC.getResolver('removeById'),
    userRemoveOne: UserTC.getResolver('removeOne'),
    userRemoveMany: UserTC.getResolver('removeMany'),
};

export { UserQuery, UserMutation };

user.js

Next, let’s create one called task.js:

import { Task, TaskTC } from '../models/task';

const TaskQuery = {
    taskById: TaskTC.getResolver('findById'),
    taskByIds: TaskTC.getResolver('findByIds'),
    taskOne: TaskTC.getResolver('findOne'),
    taskMany: TaskTC.getResolver('findMany'),
    taskCount: TaskTC.getResolver('count'),
    taskConnection: TaskTC.getResolver('connection'),
    taskPagination: TaskTC.getResolver('pagination'),
};

const TaskMutation = {
    taskCreateOne: TaskTC.getResolver('createOne'),
    taskCreateMany: TaskTC.getResolver('createMany'),
    taskUpdateById: TaskTC.getResolver('updateById'),
    taskUpdateOne: TaskTC.getResolver('updateOne'),
    taskUpdateMany: TaskTC.getResolver('updateMany'),
    taskRemoveById: TaskTC.getResolver('removeById'),
    taskRemoveOne: TaskTC.getResolver('removeOne'),
    taskRemoveMany: TaskTC.getResolver('removeMany'),
};

export { TaskQuery, TaskMutation };

task.js

To tie things together, we’ll generate an index.js file in the root of the directory (src/schema) and import our schemas:

import { SchemaComposer } from 'graphql-compose';

import db from '../utils/db'; // eslint-disable-line no-unused-vars

const schemaComposer = new SchemaComposer();

import { UserQuery, UserMutation } from './user';
import { TaskQuery, TaskMutation } from './task';

schemaComposer.Query.addFields({
    ...UserQuery,
    ...TaskQuery,
});

schemaComposer.Mutation.addFields({
    ...UserMutation,
    ...TaskMutation,
});

export default schemaComposer.buildSchema();

index.js

Now that we have full CRUD capabilities with GraphQL, let’s add our final utilities.

Build Script

The build script allows you to transform your Mongoose-style schemas into pure GraphQL schemas. Pretty fancy, huh?

Create a file called buildSchema.js inside of src/scripts, and drop the following code in:

import fs from 'fs-extra';
import path from 'path';
import { graphql } from 'graphql';
import { introspectionQuery, printSchema } from 'graphql/utilities';

import Schema from '../schema';

async function buildSchema() {
    await fs.ensureFile('../data/schema.graphql.json');
    await fs.ensureFile('../data/schema.graphql');

    fs.writeFileSync(
        path.join(__dirname, '../data/schema.graphql.json'),
        JSON.stringify(await graphql(Schema, introspectionQuery), null, 2)
    );

    fs.writeFileSync(
        path.join(__dirname, '../data/schema.graphql.txt'),
        printSchema(Schema)
    );
}

async function run() {
    await buildSchema();
    console.log('Schema build complete!');
}

run().catch(e => {
    console.log(e);
    process.exit(0);
});

buildSchema.js

This file will be called with the yarn build command and will output the raw GraphQL queries into a data directory.

Database Connectivity

What’s an API without a database? That’s why we’ll need to create a connection from Mongoose to MongoDB.

If you haven’t already created a .env file in the root directory, now’s the time to do so. You’ll want to ensure it has the following environment variables:

NODE_ENV=development
PORT=8000
MONGODB_URI=YOUR_MONGODB_URI

.env

Once your .env file’s in place, let’s go ahead and create another file inside of src/utils. Name the file db.js, and add the following contents:

import mongoose from 'mongoose';
import dotenv from 'dotenv';

dotenv.config();

mongoose.Promise = global.Promise;

const connection = mongoose.connect(process.env.MONGODB_URI, {
    autoIndex: true,
    reconnectTries: Number.MAX_VALUE,
    reconnectInterval: 500,
    poolSize: 50,
    bufferMaxEntries: 0,
    keepAlive: 120,
    useNewUrlParser: true,
});

mongoose.set('useCreateIndex', true);

connection
    .then(db => db)
    .catch(err => {
        console.log(err);
    });

export default connection;

db.js

Note: If you don’t have MongoDB up and running locally, MongoDB Atlas is a great alternative. Not only is it free, but it packs enough power on the free tier to run a development application without any issues. Check it out here.

The Playground

Your GraphQL is now complete. Run the command yarn dev, and you’ll be able to spin up the playground for GraphQL, which allows you to add, modify, remove, and query users and tasks — all in one call.

It looks a little something like this:

Conclusion

This quick-and-dirty tutorial is just the beginning of all the fun you can have using GraphQL to make your development stronger, cleaner, and more efficient.

Try expanding on what you’ve just built to add additional functionality to the models, or venture out on your own to improve one of your existing applications — or even spin up a new one; I’d love to hear more about all that you decide to do.

Until then, thank you for following me along throughout this tutorial, and stay tuned for future updates. Happy coding!