Siegfried Grimbeek
May 23 · 10 min read
Image credit: Designed by Dooder

This tutorial is Part 2 in a series that aims to help you deploy a fully functional full-stack application.

  • Part 1: How To Build Blazing-Fast REST APIs With Node.js, MongoDB, Fastify, and Swagger
  • Part 2: How To Build a Blazing-Fast GraphQL API With GraphQL, Node.js, MongoDB, and Fastify (You are here.)
  • Part 3: Coupling Vue.js With a GraphQL API
  • Part 4: Deploying a GraphQL API and Vue.js Front-End Application

The first part of the series is available here, and the source code for the application can be found here.

In this part, we will revisit the models, controllers, and routes from Part 1 and then integrate GraphQL into the application. As a bonus we will also use Faker.js to create some fake data and seed the database.


Introduction

GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data.

Every GraphQL query goes through three phases: The queries are parsed, validated, and executed.

GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need, makes it easier to evolve APIs over time, and enables powerful developer tools.

https://www.howtographql.com/basics/1-graphql-is-the-better-rest/

Prerequisites

If you have completed the first part of this series, you should be up to speed with beginner/intermediate JavaScript, Node.js, Fastify.JS, and MongoDB (Mongoose).

To follow along, you will need to complete Part 1 of this series, or grab the code from GitHub — although I would highly recommend at least skimming through Part 1.


Let’s Get Started

Clone the repo for Part 1(skip this step if you followed Part 1, and you are continuing with your own code) by opening your terminal, navigating to your project directory, and executing each of the following lines of code:

git clone https://github.com/siegfriedgrimbeek/fastify-api.git
cd fastify-api

So now that we have a copy of the codebase, we will update our packages and package.json file by running the following code:

sudo npm i -g npm-check-updates
ncu -u
npm install

First, we globally install the npm package npm-check-updates, and then we use this package to automatically update our package.json file with the latest package versions. Then we install/update all our npm modules by running npm install.

This is done to ensure that everyone completing the tutorial is working with the same package versions.


Refactor Our Server and Start the App

As with all software solutions, as the solution grows, developers often need to revisit and refactor the code.

In the src directory, we will create a new file called server.js:

cd src
touch server.js

Add the following code to the server.js file:

server.js

We have now extracted the logic that starts the server to the server.js file, allowing us to reuse this code throughout the project.

Next, we need to update our index.js file in the src directory:

index.js

We will revisit the index.js file once we set up and configure GraphQL.

Start the Fastify server by running the following code in your terminal:

npm start

Note that there is no default route setup so for now. Navigating to http://localhost:3000/ will result in the server returning a 404 error, which is correct.


Start MongoDB and Update the Models

Let’s extend the existing model to also include services and owners. The diagram below demonstrates the relationships between the collections:

Data models
  • One car can have one owner
  • One owner can have many cars
  • One car can have many services

Revisit the Car.js file in the models directory, and update it as follows:

Car.js

Create two new files in the models directory, Owner.js and Service.js, and add the following code to the files respectively:

Owner.js

Owner.js

Service.js

Service.js

There are no new concepts used in the above code. We have just created standard Mongoose schemas, as with the Car.js model.


Revisit the Car Controller and Create the Additional Controllers

There are some slight changes to the carController.js file. So navigate to the controllers directory, and update your file as per below:

carController.js

Create two new files in the controllers directory, serviceController.js and ownerController.js, and add the following code to the files respectively:

serviceController.js

serviceController..js

ownerController.js

ownerController.js

The biggest change to the controllers is how we get the parameters:

const id = req.params === undefined ? req.id : req.params.id
const updateData = req.params === undefined ? req : req.params

The above code is called a conditional (ternary) operator and is used as shorthand for the following if statement:

let idif (req.params === undefined) {id = req.id} else {id = req.params.id}

We are using the ternary operator to accommodate requests from both the REST API and the GraphQL API, as they have slightly different implementations.


Time to Seed the Database With Some Fake Data

In the src directory, let’s create a new directory and file by running the following code:

mkdir helpers
touch seed.js

Add the following code to the seed.js file:

seed.js

Let’s break down this mountain of code:

First, we import two external libraries — Faker.js, which is used to generate fake data, and Boom, which is used to throw HTTP-friendly error objects.

Then we import the server.js file — which will spin up an instance of our server, allowing us to interact with the models.

We then declare two arrays with fake data, cars and serviceGarages.

Then we import the models and declare three functions (generateOwnerData, generateCarData, and generateServiceData), which each return an array of objects with the owner, car, and service data respectively.

Once the Fastify.js instance is ready, we use the Mongoose insertMany() function to insert the generated arrays into the database. The function then returns an array of objects containing the original object data and ids of each record.

We use the JavaScript map function to create an array of ids for owner and car arrays. We use the ownersIDs array for generating car data, and we use the carsIds array when generating service data. They are passed into the respective functions, and then values are randomly selected from them.

Lastly, we need to install the Faker.js package and add the seed task to our package.json file.

We can add the Faker.js package by navigating to the root directory and running the following code:

npm i faker -D

We then add the following to the package.json file:

..."scripts": {..."seed": "node ./src/helpers/seed.js"},...

That’s it. We can now run our seeding script from the project root directory with the following code:

npm run seed

If you are using MongoDB Compass (you should), you will see the data in your database:

“mycargarage” database viewed in MongoDB Compass

GraphQL Installation, Setup, and Testing

Let’s get started by navigating to the root directory and running the following code:

npm i fastify-gql graphql

The above installs GraphQL and the Fastify barebone GraphQL adapter.

Navigate to the src directory, and run the following code:

mkdir schema
cd shema
touch index.js

Navigate to the src directory, and update the index.js file with the following:

// Import Server
const fastify = require('./server.js')
// Import external dependancies
const gql = require('fastify-gql')
// Import GraphQL Schema
const schema = require('./schema')
// Register Fastify GraphQL
fastify.register(gql, {
schema,
graphiql: true
})
... end here// Import Routes
const routes = require('./routes')

With the above code, we require the Fastify GraphQL adapter, so import the schema and register the GraphQL adapter with Fastify.

We register the schema and enable GraphiQL, an in-browser IDE for exploring GraphQL.

Navigate to the schema directory, and open the index.js file. Add the following boilerplate code:

index.js

Let’s run through the above code:

We require the main GraphQL package and use JavaScript destructuring to get the necessary GraphQL functions (GraphQLSchema, GraphQLObjectType, GraphQLString, GraphQLInt, GraphQLID, GraphQLList, and GraphQLNonNull).

We import our three controllers (carController, ownerController, and serviceController).

We declare the carType, ownerType, and serviceType GraphQL object types, which are functions that accept an object as a parameter, with a name and a fields key.

These functions are used to define our GraphQL schema, similar to the Mongoose models defined earlier.

The fields can return a particular type and methods that take arguments.

Then we declare the RootQuery, which is also a GraphQL object type, and is found at the top level of every GraphQL server. It represents all of the possible entry points into the GraphQL API.

We then declare our Mutations, which are used to change data. Although any query could be implemented to change data, operations that cause changes should be sent explicitly via a mutation.

Lastly, we export the GraphQLSchema.

Now that we have our template set up, we can start populating the object types, root query, and mutations.

Note that there are Mongoose-to-GraphQL schema generators available, but for the tutorial purposes, we will manually create the schema.

Let’s update the carType object type as follows:

carType Object Type

Let’s dive deeper into the GraphQL functions, starting with the Scalars types in GraphQL:

GraphQL comes with a set of default scalar types out of the box:

  • Int: A signed 32‐bit integer. GraphQLInt
  • Float: A signed double-precision floating-point value. GraphQLFloat
  • String: A UTF‐8 character sequence. GraphQLString
  • Boolean: true or false. GraphQLBoolean
  • ID: The ID scalar type represents a unique identifier, often used to refetch an object or as the key for a cache. The ID type is serialised in the same way as a string; however, defining it as an ID signifies that it is not intended to be human‐readable. GraphQLID

The owner and service fields are where it gets interesting. These fields are not defined as Scalar types like the rest — instead, their type is referencing the ownerType and serviceType that we have created and are yet to populate.

The second argument that we pass into the owner and service fields are resolver functions.

Resolver functions or methods are functions that resolve a value for a type or field in a schema.

Resolvers can be asynchronous too. They can resolve values from another REST API, database, cache, constant, etc. According to GraphQL’s documentation:

You can think of each field in a GraphQL query as a function or method of the previous type which returns the next type. In fact, this is exactly how GraphQL works. Each field on each type is backed by a function called the resolver which is provided by the GraphQL server developer. When a field is executed, the corresponding resolver is called to produce the next value.

If a field produces a scalar value like a string or number, then the execution completes. However if a field produces an object value then the query will contain another selection of fields which apply to that object. This continues until scalar values are reached. GraphQL queries always end at scalar values.

In order to create a relationship between the different types, we pass the _id and the owner_id values into the respective controller functions.

So essentially, we are requesting the owner details along with the car details:

return await userController.getSingleOwner({ id: parent.owner_id })

And the details of all the services related to the car:

return await serviceController.getCarsServices({ id: parent._id })

To return a list or array from within GraphQL, we use the GraphQLList. Here is a great in-depth tutorial about using arrays in the GraphQL schema, but either way, it’s really simple. Whenever we need an array we will use the GraphQLList function.

Let’s update the ownerType and serviceType with the following code:

ownerType

ownerType Object Type

serviceType

serviceType Object Type

The above two object types are very similar to the carType. You can notice a pattern between the different object types and their relationships.

We can now populate the RootQuery root with the following code:

rootQuery Object Type

There are no new concepts in the above code, but keep in mind, that the RootQuery query is the entry point to all queries on the GraphQL API. So from the above, we can see that we can run the following queries directly:

  • Get all the cars
  • Get a single car
  • Get a single owner
  • Get a single service

Let’s open the GraphiQL user interface and build some queries: http://localhost:3000/graphiql.html

Queries are entered on the left, results are in the middle, and the documentation explorer is on the right.

The documentation explorer can be used to explore the entire graph down to Scalar level. This is very helpful when building queries.

The language used to build the queries resembles JSON. This cheat sheet is a great reference.

The example below demonstrates why GraphQL is so awesome:

GraphiQL IDE

In the above example, we are using the cars root query to display a list of all the cars, their owners, and their services.

Get a single car — car root query
Get a single owner — owner root query
Get a single service — service root query

We have one final topic to address, and that is mutations. Let’s update the mutations with the following code:

mutations

As before, we declare our object type and specify the name and the fields.

A mutation consists of the type, args, and the async resolve function. The resolve function passes the args to the controller, which returns the result of the mutation.

addCar mutation
editCar mutation
deleteCar mutation

You have now coded a fully functional REST API and a fully functional GraphQL API.

There are no rules stating that one should use exclusively REST or exclusively GraphQL. In some projects, the best solution may be a mix of both. This is really determined on a project-to-project basis.

You can download the source code form GitHub here.


What’s Next?

In the next tutorial, we will consume our GraphQL API with a Vue.js front-end as a single-page application.

Better Programming

Advice for programmers.

Thanks to André Reis

Siegfried Grimbeek

Written by

Web developer, open source enthusiast and agile evangelist. Currently dreaming big and making things happen @beerwulfwebshop.

Better Programming

Advice for programmers.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade