I’ve been hearing a lot of buzz around working with GraphQL and NoSQL, internally within Couchbase and externally. Since it sounds like a hot topic, I figured I’d spend some time learning about it to see how it could be valuable when creating web applications.
Coming from the world of RESTful APIs, GraphQL is a totally different concept to me even though it aims to solve a similar problem of accessing and mutating data via client facing applications. Fortunately, Node.js and databases work hand-in-glove, so in the GraphQL-database example below we’ll be using Node.js with Couchbase as our NoSQL database layer.
As it stands right now, I’m not a GraphQL expert. I’ve been developing RESTful APIs with frameworks like Express and Hapi for many years. That said, I’m going to try to explain how I’ve learned about them.
APIs work great, but when it comes to getting the information you need out of them, things can be done better. For example, what if you need a bunch of unrelated or loosely related data for your client? Do you create one massive endpoint that returns a lot of data or do you make a request to every API endpoint that might contain your data? What if you need only a fragment of the data returned from any particular endpoint?
This is where GraphQL comes into play. Through the query language you can specify exactly what data you want returned to the client at any given time without writing an endpoint for everything.
Creating a New Node.js Application with Express Framework
We’re going to be using Node.js to create an interface for using GraphQL queries. Let’s start by creating a new project, getting the dependencies and bootstrapping our project in preparation for some real logic.
With Node.js installed, from the command line, execute:
1 2 3 4 5 6 |
npm init -y npm install couchbase --save npm install express --save npm install express-graphql --save npm install graphql --save npm install uuid --save |
The above commands will initialize a new project by creating a package.json file and installing the necessary dependencies. Of the dependencies, we’re installing Couchbase and a package for generating UUID strings that will later represent our document keys. The other packages are related to GraphQL and linking it to Express Framework.
Create a file called app.js within your project and include the following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
const Express = require("express"); const Couchbase = require("couchbase"); const ExpressGraphQL = require("express-graphql"); const BuildSchema = require("graphql").buildSchema; const UUID = require("uuid"); var schema = BuildSchema(``); var resolvers = { }; var app = Express(); app.use("/graphql", ExpressGraphQL({ schema: schema, rootValue: resolvers, graphiql: true })); app.listen(3000, () => { console.log("Listening at :3000"); }); |
We’re not going to worry about populating our schema and configuring our database quite yet. In the above code, notice that we’ve imported each of our dependencies and initialized Express. We’ve also defined a single API endpoint which is how we’re going to get Couchbase and GraphQL to interact. Essentially, we’re going to issue every GraphQL query to that single endpoint and it will respond with the correct data based on our schema and resolvers.
Defining a GraphQL Schema and Resolvers
Now that we have the application foundation in place, let’s figure out a schema and the resolvers for actually linking the data to potential queries and data types.
For this example, we’re going to create a simple blogging application. For this reason, it might make sense to have the following schema models:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
var schema = BuildSchema(` type Account { id: String, firstname: String, lastname: String, } type Blog { id: String, account: String!, title: String, content: String } `); |
We know we’ll have accounts and we know that we’ll have blog articles for any given account. The exclamation mark for any data type means that it is a required property.
Now we need to hook our model up to Couchbase so we can query for data or even create data.
Querying and Mutating Data from Couchbase Server, the NoSQL Database
We’re going to assume that you already have Couchbase running. There is no special configuration requirement of Couchbase for this particular example.
The first step is to connect to Couchbase Server from the Node.js application. Within the project’s app.js file, add the following lines:
1 2 3 |
var cluster = new Couchbase.Cluster("couchbase://localhost"); cluster.authenticate("example", "123456"); var bucket = cluster.openBucket("example"); |
The above lines assume Node.js and the database — Couchbase, in this example — are running on the local machine. We’re going to be using a Bucket called example
and an RBAC user account called example
.
The data models for GraphQL are already in place, but we haven’t defined the possible queries or mutations. Let’s modify the schema in our application to look like the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
var schema = BuildSchema(` type Query { account(id: String!): Account, accounts: [Account], blog(id: String!): Blog, blogs(account: String!): [Blog] } type Account { id: String, firstname: String, lastname: String, } type Blog { id: String, account: String!, title: String, content: String } type Mutation { createAccount(firstname: String!, lastname: String!): Account createBlog(account: String!, title: String!, content: String!): Blog } `); |
For both the queries and mutations, special resolver functions will be called which do the heavy lifting. As a result, either an Account
or a Blog
will be returned.
Starting with the mutations, we are expecting a createAccount
function that expects two parameters. It might look like the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
var resolvers = { createAccount: (data) => { var id = UUID.v4(); data.type = "account"; return new Promise((resolve, reject) => { bucket.insert(id, data, (error, result) => { if(error) { return reject(error); } resolve({ "id": id }); }); }); } }; |
Notice that our resolver function only has one parameter even though we were passing two in our schema. All of our parameters for data will reside in the data
variable. The function itself expects that we return a promise.
You’ll notice that we are returning an id
and nothing else. Remember, we expect an Account
to be returned, but not every part of Account
needs to be present. However, it might be better practice to return everything.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
var resolvers = { createBlog: (data) => { var id = UUID.v4(); data.type = "blog"; return new Promise((resolve, reject) => { bucket.insert(id, data, (error, result) => { if(error) { return reject(error); } resolve({ "id": id }); }); }); } }; |
The createBlog
function is pretty much the same as the previous function, with the exception that the input data is different and the type
property is set to something else. All our functions will be in the same resolvers
object.
With the mutations out of the way, the data can be queried. Take the account
function for example:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
var resolvers = { account: (data) => { var id = data.id; return new Promise((resolve, reject) => { bucket.get(id, (error, result) => { if(error) { return reject(error); } resolve(result.value); }); }); } }; |
Nothing really different is happening between this particular query function versus the mutation function. We’re expecting an id
to be passed and we’re using it to do a lookup.
For retrieving all accounts, we can use a N1QL query:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
var resolvers = { accounts: () => { var statement = "SELECT META(account).id, account.* FROM `" + bucket._name + "` AS account WHERE account.type = 'account'"; var query = Couchbase.N1qlQuery.fromString(statement); return new Promise((resolve, reject) => { bucket.query(query, (error, result) => { if(error) { return reject(error); } resolve(result); }); }); } }; |
Can you see the trend now when it comes to querying and mutating data? Just to finish the job, let’s look at our two final functions, one for getting a single blog and one for getting all blogs.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
var resolvers = { blog: (data) => { var id = data.id; return new Promise((resolve, reject) => { bucket.get(id, (error, result) => { if(error) { return reject(error); } resolve(result.value); }); }); }, blogs: (data) => { var statement = "SELECT META(blog).id, blog.* FROM `" + bucket._name + "` AS blog WHERE blog.type = 'blog' AND blog.account = $account"; var query = Couchbase.N1qlQuery.fromString(statement); return new Promise((resolve, reject) => { bucket.query(query, { "account": data.account }, (error, result) => { if(error) { return reject(error); } resolve(result); }); }); } }; |
At this point in time our application is complete. At least complete enough for this particular example. Now we should probably test it with some GraphQL queries.
Executing GraphQL Queries with the Graphical Interface
If you’re using Node.js and the GraphQL package for Express, you’ll get a nice graphical interface for executing queries. Navigate to http://localhost:3000/graphql in your web browser and you should see something that looks like the following:
So let’s create a mutation query that will insert data for us.
1 2 3 4 5 |
mutation CreateAccount($firstname: String!, $lastname: String!) { createAccount(firstname: $firstname, lastname: $lastname) { id } } |
The above mutation will take in two mandatory parameters and then execute our createAccount
function passing each of those parameters. The parameters can be set in the Query Variables section of the GraphQL explorer. The parameters would look something like this:
1 2 3 4 |
{ "firstname": "Erika", "lastname": "Raboy" } |
Upon a successful mutation, we’ll get the id
which is what we requested. After we’ve filled our database with data, we could do a more sophisticated GraphQL query like the following:
1 2 3 4 5 6 7 8 9 10 |
query GetAccountData($id: String!) { account(id: $id) { firstname lastname } blogs(account: $id) { title content } } |
For the above query you’d pass in a valid account and it would return the account information as well as the blog information that that account had written. Through the GraphQL queries you can request certain properties or all properties while saving you from making multiple requests.
Conclusion
You just saw how to make a simple Node.js database leveraging GraphQL in Couchbase. Just because GraphQL gives you a means to query for the data that you need, it doesn’t eliminate the need to write quality queries that communicate with your database. Remember, GraphQL is just a client facing query language, not a backend database query language. In other words, let Couchbase do the heavy lifting where it makes sense.
If you’d like more help with the Node.js SDK for Couchbase, check out the Couchbase Developer Portal.