In this article by Samer Buna author of the book Learning GraphQL and Relay, we're mostly going to be talking about how an API is nothing without access to a database. Let's set up a local MongoDB instance, add some data in there, and make sure we can access that data through our GraphQL schema.
(For more resources related to this topic, see here.)
MongoDB can be locally installed on multiple platforms. Check the documentation site for instructions for your platform (https://docs.mongodb.com/manual/installation/).
For Mac, the easiest way is probably Homebrew:
~ $ brew install mongodb
Create a db folder inside a data folder. The default location is /data/db:
~ $ sudo mkdir -p /data/db
Change the owner of the /data folder to be the current logged-in user:
~ $ sudo chown -R $USER /data
Start the MongoDB server:
~ $ mongod
If everything worked correctly, we should be able to open a new terminal and test the mongo CLI:
~/graphql-project $ mongo
MongoDB shell version: 3.2.7
connecting to: test
> db.getName()
test
>
We're using MongoDB version 3.2.7 here. Make sure that you have this version or newer versions of MongoDB.
Let's go ahead and create a new collection to hold some test data. Let's name that collection users:
> db.createCollection("users")"
{ "ok" : 1 }
Now we can use the users collection to add documents that represent users. We can use the MongoDB insertOne() function for that:
> db.users.insertOne({
firstName: "John","
lastName: "Doe","
email: "john@example.com"
})
We should see an output like:
{
"acknowledged" : true,
"insertedId" : ObjectId("56e729d36d87ae04333aa4e1")
}
Let's go ahead and add another user:
> db.users.insertOne({
firstName: "Jane","
lastName: "Doe","
email: "jane@example.com"
})
We can now verify that we have two user documents in the users collection using:
> db.users.count()
2
MongoDB has a built-in unique object ID which you can see in the output for insertOne().
Now that we have a running MongoDB, and we have some test data in there, it's time to see how we can read this data using a GraphQL API.
To communicate with a MongoDB from a Node.js application, we need to install a driver. There are many options that we can choose from, but GraphQL requires a driver that supports promises. We will use the official MongoDB Node.js driver which supports promises. Instructions on how to install and run the driver can be found at: https://docs.mongodb.com/ecosystem/drivers/node-js/.
To install the MongoDB official Node.js driver under our graphql-project app, we do:
~/graphql-project $ npm install --save mongodb
└─┬ mongodb@2.2.4
We can now use this mongodb npm package to connect to our local MongoDB server from within our Node application. In index.js:
const mongodb = require('mongodb');
const assert = require('assert');
const MONGO_URL = 'mongodb'://localhost:27017/test';
mongodb.MongoClient.connect(MONGO_URL, (err, db) => {
assert.equal(null, err);
console.log('Connected' to MongoDB server');
// The readline interface code
});
The MONGO_URL variable value should not be hardcoded in code like this. Instead, we can use a node process environment variable to set it to a certain value before executing the code. On a production machine, we would be able to use the same code and set the process environment variable to a different value.
Use the export command to set the environment variable value:
export MONGO_URL=mongodb://localhost:27017/test
Then in the Node code, we can read the exported value by using:
process.env.MONGO_URL
If we now execute the node index.js command, we should see the Connected to MongoDB server line right before we ask for the Client Request.
At this point, the Node.js process will not exit after our interaction with it. We'll need to force exit the process with Ctrl + C to restart it.
Let's start our database API with a simple field that can answer this question: How many total users do we have in the database?
The query could be something like:
{ usersCount }
To be able to use a MongoDB driver call inside our schema main.js file, we need access to the db object that the MongoClient.connect() function exposed for us in its callback. We can use the db object to count the user documents by simply running the promise:
db.collection('users').count()
.then(usersCount => console.log(usersCount));
Since we only have access to the db object in index.js within the connect() function's callback, we need to pass a reference to that db object to our graphql() function. We can do that using the fourth argument for the graphql() function, which accepts a contextValue object of globals, and the GraphQL engine will pass this context object to all the resolver functions as their third argument. Modify the graphql function call within the readline interface in index.js to be:
graphql.graphql(mySchema, inputQuery, {}, { db }).then(result => {
console.log('Server' Answer :', result.data);
db.close(() => rli.close());
});
The third argument to the graphql() function is called the rootValue, which gets passed as the first argument to the resolver function on the top level type. We are not using that feature here.
We passed the connected database object db as part of the global context object. This will enable us to use db within any resolver function.
Note also how we're now closing the rli interface within the callback for the operation that closes the db. We should not leave any open db connections behind.
Here's how we can now use the resolver third argument to resolve our usersCount top-level field with the db count() operation:
fields: {
// "hello" and "diceRoll"..."
usersCount: {
type: GraphQLInt,
resolve: (_, args, { db }) =>
db.collection('users').count()
}
}
A couple of things to notice about this code:
We can test our query now:
~/graphql-project $ node index.js
Connected to MongoDB server
Client Request: { usersCount }
Server Answer : { usersCount: 2 }
*** #GitTag: chapter1-setting-up-mongodb ***
Let's now see how we can use the graphql() function under another interface, an HTTP one.
We want our users to be able to send us a GraphQL request via HTTP. For example, to ask for the same usersCount field, we want the users to do something like:
/graphql?query={usersCount}
We can use the Express.js node framework to handle and parse HTTP requests, and within an Express.js route, we can use the graphql() function. For example (don't add these lines yet):
const app = express();
app.use('/graphql', (req, res) => {
// use graphql.graphql() to respond with JSON objects
});
However, instead of manually handling the req/res objects, there is a GraphQL Express.js middleware that we can use, express-graphql. This middleware wraps the graphql() function and prepares it to be used by Express.js directly. Let's go ahead and bring in both the Express.js library and this middleware:
~/graphql-project $ npm install --save express express-graphql
├─┬ express@4.14.0
└─┬ express-graphql@0.5.3
In index.js, we can now import both express and the express-graphql middleware:
const graphqlHTTP = require('express-graphql');
const express = require('express');
const app = express();
With these imports, the middleware main function will now be available as graphqlHTTP(). We can now use it in an Express route handler. Inside the MongoClient.connect() callback, we can do:
app.use('/graphql', graphqlHTTP({
schema: mySchema,
context: { db }
}));
app.listen(3000, () =>
console.log('Running Express.js on port 3000')
);
Note that at this point we can remove the readline interface code as we are no longer using it. Our GraphQL interface from now on will be an HTTP endpoint.
The app.use line defines a route at /graphql and delegates the handling of that route to the express-graphql middleware that we imported. We pass two objects to the middleware, the mySchema object, and the context object. We're not passing any input query here because this code just prepares the HTTP endpoint, and we will be able to read the input query directly from a URL field.
The app.listen() function is the call we need to start our Express.js app. Its first argument is the port to use, and its second argument is a callback we can use after Express.js has started.
We can now test our HTTP-mounted GraphQL executor with:
~/graphql-project $ node index.js
Connected to MongoDB server
Running Express.js on port 3000
In a browser window go to:
http://localhost:3000/graphql?query={usersCount}
*** #GitTag: chapter1-setting-up-an-http-interface ***
The graphqlHTTP() middleware function accepts another property on its parameter object graphiql, let's set it to true:
app.use('/graphql', graphqlHTTP({
schema: mySchema,
context: { db },
graphiql: true
}));
When we restart the server now and navigate to http://localhost:3000/graphql, we'll get an instance of the GraphiQL editor running locally on our GraphQL schema:
GraphiQL is an interactive playground where we can explore our GraphQL queries and mutations before we officially use them. GraphiQL is written in React and GraphQL, and it runs completely within the browser.
GraphiQL has many powerful editor features such as syntax highlighting, code folding, and error highlighting and reporting. Thanks to GraphQL introspective nature, GraphiQL also has intelligent type-ahead of fields, arguments, and types.
Put the cursor in the left editor area, and type a selection set:
{
}
Place the cursor inside that selection set and press Ctrl + space. You should see a list of all fields that our GraphQL schema support, which are the three fields that we have defined so far (hello, diceRoll, and usersCount):
If Ctrl +space does not work, try Cmd + space, Alt + space, or Shift + space.
The __schema and __type fields can be used to introspectively query the GraphQL schema about what fields and types it supports.
When we start typing, this list starts to get filtered accordingly. The list respects the context of the cursor, if we place the cursor inside the arguments of diceRoll(), we'll get the only argument we defined for diceRoll, the count argument.
Go ahead and read all the root fields that our schema support, and see how the data gets reported on the right side with the formatted JSON object:
*** #GitTag: chapter1-the-graphiql-editor ***
In this article, we learned how to set up a local MongoDB instance, add some data in there, so that we can access that data through our GraphQL schema.
Further resources on this subject: