How to build a GraphQl API from scratch with Node JS, Express, and MongoDB

Ibrahima Ndaw - Apr 3 '20 - - Dev Community

In this guide, we will build from scratch an API with GraphQL, Node JS, Express, and MongoDB. So, let's start by answering an important question: what is GraphQL?

Originally posted on my blog

What is GraphQL?

GraphQL is a query language created by Facebook. It's an alternative to the REST approach.
So, if you come from the REST world, just keep in mind that GraphQL works differently. It has one single endpoint for all kinds of requests, and the method has to be a post request. GraphQL works with types and fields, and it's really powerful since it provides all or just the needed data.

Send a GraphQL query to your API and get exactly what you need, nothing more and nothing less.

We will see it in action later, but for now, let's plan our API.

Setting up the GraphQL API

For the API, we will have the possibility to create articles and store them in MongoDB. And also be able to fetch them back.

To do so, we have to create a new project by running the following command in the terminal.

    yarn init
Enter fullscreen mode Exit fullscreen mode

In this tutorial, I will use yarn, you can use npm if you want too. It's really up to you

Next, structure your project as follow:

├── node_modules
├── graphql
|  ├── resolvers
|  |  └── index.js
|  └── schema
|     └── index.js
├── models
|  └── article.js
├── app.js
├── nodemon.json
├── package.json
└── yarn.lock
Enter fullscreen mode Exit fullscreen mode

As you can see, we have a graphql folder that keeps the schema and the resolvers of the API.

Next, we have a models folder which holds what an article should looks like and last but not the least, a nodemon.json file to hold our environment variables and the entry point of the server app.js.

We have a couple of libraries to install, so I will keep things simple and install the needed one as we progress.

Now, let's run the following commands on the terminal to install express and nodemon.

    yarn add express 
Enter fullscreen mode Exit fullscreen mode

Next, add nodemon as a development dependency.

    yarn add nodemon -D
Enter fullscreen mode Exit fullscreen mode

With that, we can now add a start script on the package.json file to be able as you might guess start the server.

  • package.json
{
  "name": "graphql-api",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "start": "nodemon app.js"
  },
  "dependencies": {
    "express": "^4.17.1",
  },
  "devDependencies": {
    "nodemon": "^2.0.2"
  }
}
Enter fullscreen mode Exit fullscreen mode

As you can see, here we use nodemon to start the server and when a file is added or updated, nodemon will react to the update automatically.

We have now the command to start the server, but still, no server to start. We will create the server later, but for now, let's define the schema of the API.

GraphQL Schema

A schema describes the shape of your data graph. It defines a set of types with fields that are populated from your back-end data stores.

And to create a schema, we need to install the graphql package by running on the terminal:

    yarn add graphql
Enter fullscreen mode Exit fullscreen mode

Next, we have to add the following code block to create a GraphQL schema.

  • graphql/schema/index.js
const { buildSchema } = require('graphql')

module.exports = buildSchema(`

  type Article {
    _id: ID!
    title: String!
    body: String!
    createdAt: String!
  }


  input ArticleInput {
    title: String!
    body: String!
  }

  type Query {
    articles:[Article!]
  }

  type Mutation {
    createArticle(article:ArticleInput): Article
  }

  schema {
    query: Query
    mutation: Mutation
  }
`)
Enter fullscreen mode Exit fullscreen mode

To create a schema, we first have to import buildSchema from graphql, and next create our types.
GraphQL works with types, it supports several scalar types.

Here, we have the type Article which must have an _id (you have to name it like this due to MongoDB) of type ID, a title, a body and a createdAt field of type String.

The exclamation mark ! just means that the type defined is required, it has to match the type.

Next, we have an input type which defines what the expected input should look like. It's the data entered by the user and will be used to create a new article.

A GraphQL query as the name suggests is used to define a query type. And here, we have an articles query to fetch the data. It should return an array, and each article has to match the type Article.

Now, to be able to fetch articles, we have to create them first. And to do so, we use a GraphQl mutation. It's a query that creates, updates or deletes data in the data store and returns a value.

And here to create a new article we use the createArticle mutation. It receives an object of type ArticleInput and returns the created article.

Now we have everything we need to create a schema, the final thing to do is to pass the Query and the Mutation to the schema.

And Voilà, we have now a schema.

However, a GraphQl schema is not enough though, we have to create another schema, a model to be precise to ensure that the data send to MongoDB matches the schema defined with GraphQL.

Creating Mongoose Models

As I mentioned earlier, MongoDB will be used as a database, and to make things easier, we will use mongoose to interact with it.

And to install it, we need to run the following command in the terminal.

  yarn add mongoose
Enter fullscreen mode Exit fullscreen mode

Next, we can now create a model for an article.

  • models/article.js
const mongoose = require('mongoose')

const Schema = mongoose.Schema

const artcleSchema = new Schema({

    title: {
        type: String,
        required: true
    },

    body: {
        type: String,
        required: true
    }

}, { timestamps: true })

module.exports = mongoose.model('Article', artcleSchema)
Enter fullscreen mode Exit fullscreen mode

To create a model of data, we first have to import mongoose and access to the Schema method.
With that, we can now create a schema for a given article. And if you remember, in our GraphQL schema we have some required fields (!), hence, here we use the required property to follow the schema defined with GraphQL.

And for the _id, we don't need to add it as a field in the schema since it will be created automatically.
It's the same thing for createdAt, the second argument timestamps: true tells to mongoose to add a createdAt and updatedAt fields to the schema.

Now, to create the model, we have to use mongoose again and pass as arguments the name of the model and the schema to the model() method.

Now, it seems like we have everything we need to create GraphQL resolvers for the API. So, let's do that in the next section.

exciting-stuff

GraphQl resolver

A resolver is a collection of functions that helps generating a response from a GraphQL query. It handles the request and returns a response. And every query or mutation name has to match exactly the name of the resolver function. That means, if we have a query named articles, we should have an articles() resolver function.

Now to create resolvers, we have to add this code block below in the graphql/resolvers/index.js file.

  • graphql/resolvers/index.js
const Article = require('../../models/article')

module.exports = {

  articles: async () => {
    try {
       const articlesFetched = await Article.find()
        return articlesFetched.map(article => {
            return {
                ...article._doc,
                _id: article.id,
                createdAt: new Date(article._doc.createdAt).toISOString() }
        })
    }
    catch (error) {
        throw error
    }
 },

  createArticle: async args => {
  try {
    const { title, body } = args.article
    const article = new Article({
        title,
        body
    })
    const newArticle = await article.save()
    return { ...newArticle._doc, _id: newArticle.id }
  }
  catch (error) {
      throw error
  }

 }
}
Enter fullscreen mode Exit fullscreen mode

In the GraphQL schema, we have a query named articles which return an array of articles. Therefore, we should have here a resolver with the same name.

The articles function uses the model created with mongoose to send the request to MongoDB. That said, we can now access .find() to well as the name suggest fetch all articles from the database.

The returned value is an array, therefore, we have to loop through it and for each object return the document (it's an object which holds the data), override the _id with mongoose and convert the createdAt field to a more user-friendly date.

And, as you already know, this operation can take time to complete, that the reason why we use async/await to handle the request.

For the second resolver function createArticle, it handles the mutation defined previously in the GraphQL schema. It receives as an argument the article object, and with that, it creates a new article based on the Article model.

And to store it to MongoDB, we just have to use another helper provided by mongoose, the save() method and return as expected in the GraphQL schema the newly created article.

By the way, the response sent by MongoDB has some metadata that's why for both functions, I return the _doc property directly.

With that change, we have now a schema and resolvers for our GraphQL API, it's pretty much what we need to move to the next section and create a server and an endpoint.

Create the server and an endpoint

In the package.json file, we have a script to start the server. And it starts with the app.js file, thus we need to update that file a bit to have a real server.

Before writing the logic of creating a server, we need to install express-graphql which is the glue between graphql and express.

And to install it, we have to run the following command in the terminal.

  yarn add express-graphql
Enter fullscreen mode Exit fullscreen mode

Next, add this code block to app.js

  • app.js
const express = require('express')
const graphqlHttp = require('express-graphql')
const graphqlSchema = require('./graphql/schema')
const graphqlResolvers = require('./graphql/resolvers')

const app = express()

app.use('/graphql', graphqlHttp({
    schema:graphqlSchema,
    rootValue:graphqlResolvers,
    graphiql: true
}))

app.listen(3000, () => console.log('Server is running on localhost:3000'))
Enter fullscreen mode Exit fullscreen mode

As you can see, here we import the schema and resolvers created earlier. And to use them, we need graphqlHttp (you can name it whatever you want). It's a method provided by express-graphql that expects some options. Here, it receives the schema and the resolver, I also enabled graphiql which is a cool tool for testing queries.

The endpoint for all requests will be /graphql, and to be able to reach that endpoint, we need to start the server and listen to the port 3000.

Great! we've now a working API, but thus far there is still something missing: the API is not connected yet to MongoDB. So, let's fix that in the next section.

nice

Connect the API to MongoDB

If you remember, in the structure folder, we had a nodemon.json file, that file will be used now to store our environment variables.

But first, you will need to create a new cluster on MongoDB Atlas and get the username and password of the database.

Next, update the credentials with yours.

  • nodemon.json.
{
    "env": {
        "MONGO_USER": "your_username",
        "MONGO_PASSWORD": "your_password",
        "MONGO_DB": "your_database"
    }
}
Enter fullscreen mode Exit fullscreen mode

Now we have the needed credentials, it's time to connect the API to MongoDB. And to do so, we have to tweak a bit app.js.

const express = require('express')
const graphqlHttp = require('express-graphql')
const mongoose = require('mongoose')
const graphqlSchema = require('./graphql/schema')
const graphqlResolvers = require('./graphql/resolvers')

const app = express()

app.use('/graphql', graphqlHttp({
    schema:graphqlSchema,
    rootValue:graphqlResolvers,
    graphiql: true
}))

const uri = `mongodb+srv://${process.env.MONGO_USER}:${process.env.MONGO_PASSWORD}@cluster0-uox7n.mongodb.net/${process.env.MONGO_DB}?retryWrites=true&w=majority`

const options = {useNewUrlParser: true, useUnifiedTopology: true}

mongoose.connect(uri, options)
        .then(() => app.listen(3000, console.log('Server is running')))
        .catch(error => { throw error })
Enter fullscreen mode Exit fullscreen mode

This link comes from MongoDB Atlas, it allows us to connect to the database. And I also use the credentials held on nodemon.json to build the uri.

Next, we use again mongoose and pass as parameters the uri and some options to the connect() method. And when the operation is successfully finished, we start the server, otherwise, an error will be thrown.

With that change, we have now connected the API to MongoDB. It's time now to test it with the GraphQL playground to see if the API works as expected.

Test the API with GraphiQL

To access the GraphQL playground, we have to start the server with the following command:

  yarn start
Enter fullscreen mode Exit fullscreen mode

Now, if you browse to http://localhost:3000/graphql, you will be able to play with GraphiQL.

  • Create a new article

To create an article, we have to send a query mutation.

mutation

Seems like it works perfectly, the mutation creates a new article and returns it as expected.

Now, let's try fetching the articles stored on MongoDB.

  • Fetch the articles

As I said earlier, GraphQL allows us to fetch all or just the fields we need.

query

And here, I want to fetch for each article the title, body and, createdAt from the database. And, as you can see the response returned by MongoDB is the expected one.

Great! We have now done building a GraphQL API from scratch with Node JS, Express, and MongoDB.

You can find the source code here

Thanks for reading

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .