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?
- What is GraphQL?
- Setting up the GraphQL API
- GraphQL Schema
- Creating Mongoose Models
- GraphQl resolver
- Create the server and an endpoint
- Connect the API to MongoDB
- Test the API with GraphiQL
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
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
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
Next, add nodemon
as a development dependency.
yarn add nodemon -D
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"
}
}
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
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
}
`)
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
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)
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.
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
}
}
}
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
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'))
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.
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"
}
}
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 })
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
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.
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.
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