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.
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.
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.
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.
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. If you’d like to learn more, I encourage you to check out my video course and eBook titled, Web Services for the JavaScript Developer.
A video version of this tutorial can be seen below.