Monday, 11 December, 2017 UTC


Summary

Recently, I have been exploring GraphQL. Apollo (client and server) has really made working with GraphQL awesome. Apollo server has support for some NodeJS frameworks out of the box. When it is comes to NodeJS frameworks, AdonisJS is my preferred choice. Unfortunately, Apollo server does not support AdonisJS out of the box. For this reason, I created an AdonisJS package called adonis-apollo-server which integrates Apollo server with the AdonisJS framework.
In this tutorial, I will show you how to build a GraphQL server with Apollo server and AdonisJS using the package above.
This tutorial assumes you have some basic understanding of GraphQL. With that said, let's jump in and start building our GraphQL server.
What We’ll be Building
For the purpose of this tutorial, we’ll build a GraphQL server with the concept of a basic blog. The blog will have User and Post entities. We’ll be able to create users and authenticate them using JSON Web Tokens (JWT). Authenticated users will be able to create posts. Hence, User and Post will have a one-to-many relationship. That is, a user can have many posts, while a post can only belong to a user.
Requirements
This tutorial assumes you have the following installed on your computer:
  • Node.js 8.0 or greater
  • Npm 3.0 or greater
Create a new AdonisJS app
We’ll start by creating a new AdonisJS app. We’ll use the adonis CLI for this, so run the command below if you don’t have it installed already:
npm i -g @adonisjs/cli
With that installed, let’s create a new AdonisJS app. We'll call it adonis-graphql-server:
adonis new adonis-graphql-server --api-only
We are specify that we want the API only app by passing api-only flag. This will create an app well suited for building APIs as things like views won’t be included.
We can start the app to make sure everything is working as expected:
cd adonis-graphql-server
adonis serve --dev
Then visit http://127.0.0.1:3333, you should get a JSON response as below:
    {
      "greeting": "Hello world in JSON"
    }
Installing Dependencies
Next, we need to install the dependencies needed for our GraphQL server.
npm install graphql adonis-apollo-server graphql-tools slugify --save
Let’s quickly go each of the dependencies.
  • graphql: a reference implementation of GraphQL for JavaScript.
  • adonis-apollo-server: apollo server for AdonisJS.
  • graphql-tools: for building a GraphQL schema and resolvers in JavaScript.
  • slugify: slugify a string.
Before we can start using the adonis-apollo-server package, we need to first register the provider inside start/app.js:
// start/app.js

const providers = [
    ...
    'adonis-apollo-server/providers/ApolloServerProvider'
]
Database Setup
We’ll be using MySQL in this tutorial. So, we need to install Node.js driver for MySQL:
npm install mysql --save
Once that’s installed, we need to make AdonisJS know we are using MySQL. Open .env and add the snippet below to it:
// .env

DB_CONNECTION=mysql
DB_HOST=localhost
DB_DATABASE=adonis_graphql_server
DB_USER=root
DB_PASSWORD=
Remember to update the database name, username and password accordingly with your own database settings.
Defining GraphQL Schema
GraphQL schema describe how data are shaped and what data on the server can be queried or modified. Schema can be of two types: Query and Mutation. As described in the “What We’ll be Building” section, we’ll define schema for User and Post types. To do this, create a new directory named data within the app directory. Within the data directory, create a new schema.js file and paste the code below in it:
// app/data/schema.js

'use strict'

const { makeExecutableSchema } = require('graphql-tools')

// Define our schema using the GraphQL schema language
const typeDefs = `
  type User {
    id: Int!
    username: String!
    email: String!
    posts: [Post]
  }
  type Post {
    id: Int!
    title: String!
    slug: String!
    content: String!
    user: User!
  }
`
The fields are pretty straightforward. The User type also has a posts field which will be an array of all posts created by a user. Similarly, the Post type also has a user field which will be the user that created a post. You’ll noticed we attached ! while defining the user field. This simply means, the user field can not be NULL or empty. That is to say, a post must be created by a user.
Having defined our types, let’s now the define the queries we want to be carried out on them. Still within app/data/schema.js, paste the code below just after the Post type:
// app/data/schema.js

type Query {
  allUsers: [User]
  fetchUser(id: Int!): User
  allPosts: [Post]
  fetchPost(id: Int!): Post
}
We are saying we want to be able to fetch all users and posts created and return them as an array. Also, fetch individual user and post by their ID respectively.
Next, we define some mutations. Mutation allows data to be modified on the server. Still within app/data/schema.js, paste the code below just after Query:
// app/data/schema.js

type Mutation {
  login (email: String!, password: String!): String
  createUser (username: String!, email: String!, password: String!): User
  addPost (title: String!, content: String!): Post
}
The login mutation will allow users to login into the server. It returns a string which will be a JWT. createUser as it name suggests simply creates a new user. addPost allows an authenticated user to create a post.
Finally, we need to build the schema. Before we do that, let’s add the resolvers (which we’ll create shortly) just after importing graphql-tools:
// app/data/schema.js

const resolvers = require('./resolvers')
Then we can build the schema:
// app/data/schema.js

// Type definition
...

module.exports = makeExecutableSchema({ typeDefs, resolvers })
Creating Models and Migration
Before we move on to writing resolvers, let’s take a moment to define and our models. We want our models to be as similar as possible to our schema. So, we’ll define User and Post models.
Luckily for us, AdonisJS ships with it a User model and migration file. We just need to create a Post model and migration file.
adonis make:model Post -m
This will create a app/Models/Post.js model and generate a migration file. Open the migration file database/migrations/xxxxxxxxxxxxx_post_schema.js and update the up method as below:
// database/migrations/xxxxxxxxxxxxx_post_schema.js

up () {
  this.create('posts', (table) => {
    table.increments()
    table.integer('user_id').unsigned().notNullable()
    table.string('title').notNullable()
    table.string('slug').unique().notNullable()
    table.text('content').notNullable()
    table.timestamps()
  })
}
We can now run the migrations:
adonis migration:run
Defining Models Relationship
Recall, we said User and Post will have a one-to-many relationship. We’ll now define the relationship. Open app/Models/User.js and add the code below within the User class just after the tokens method:
// app/Models/user.js

/**
 * A user can have many posts.
 *
 * @method posts
 *
 * @return {Object}
 */
posts () {
  return this.hasMany('App/Models/Post')
}
Also, let's define the inverse relationship. Open app/Models/Post.js and add the code below within the Post class. This will be the only method the Post model will have:
// app/Models/Post.js

/**
 * A post belongs to a user.
 *
 * @method user
 *
 * @return {Object}
 */
user () {
  return this.belongsTo('App/Models/User')
}
Writing Resolvers
With our models and their relationship defined, we can now move on to writing the resolvers. A resolver is a function that defines how a field in a schema is executed. Within app/data directory, create a new resolvers.js file and paste the code below it it:
// app/data/resolvers.js

'use strict'

const User = use('App/Models/User')
const Post = use('App/Models/Post')
const slugify = require('slugify')

// Define resolvers
const resolvers = {
  Query: {
    // Fetch all users
    async allUsers() {
      const users = await User.all()
      return users.toJSON()
    },
    // Get a user by its ID
    async fetchUser(_, { id }) {
      const user = await User.find(id)
      return user.toJSON()
    },
    // Fetch all posts
    async allPosts() {
      const posts = await Post.all()
      return posts.toJSON()
    },
    // Get a post by its ID
    async fetchPost(_, { id }) {
      const post = await Post.find(id)
      return post.toJSON()
    }
  },
}
Firstly, we imported our models and the slugify package. Then we start by writing functions for retrieve our queries. Lucid (AdonisJS ORM) makes use of serializers. So, whenever we query the database using Lucid models, the return value is always a serializer instance. Hence, the need for calling toJSON() so as to return a formatted output.
Next, we define functions for our mutations. Still within app/data/resolvers.js, paste the code below just after Query object:
// app/data/resolvers.js

Mutation: {
  // Handles user login
  async login(_, { email, password }, { auth }) {
    const { token } = await auth.attempt(email, password)
    return token
  },

  // Create new user
  async createUser(_, { username, email, password }) {
    return await User.create({ username, email, password })
  },

  // Add a new post
  async addPost(_, { title, content }, { auth }) {
    try {
      // Check if user is logged in
      await auth.check()

      // Get the authenticated user
      const user = await auth.getUser()

      // Add new post
      return await Post.create({
        user_id: user.id,
        title,
        slug: slugify(title, { lower: true }),
        content
      })
    } catch (error) {
      // Throw error if user is not authenticated
      throw new Error('Missing or invalid jwt token')
    }
  }
},
Because we created an api-only app, the app is already configured to use of JWT for authentication.
Note: We need to set the Authorization = Bearer <token> header to authenticate the request.
The login function makes use of the auth object which will be pass to the context object of GraphQL option when starting the server (more on this later). It then attempt to log the user in. If it was successful, a token (JWT) will be returned. The createUser function simply creates a new user in the database. Lastly, the addPost function, just like login(), also accepts the auth object as it third argument. It checks if the user is logged in, then get the details of the authenticated user and finally add a new post to the database. If the user is not logged in (that is, token was not found or is invalid) we return an error message.
Next, we define functions to retrieve the fields on our User, Post types respectively. Still within app/data/resolvers.js, paste the code below just after Mutation object:
// app/data/resolvers.js

User: {
  // Fetch all posts created by a user
  async posts(userInJson) {
    // Convert JSON to model instance
    const user = new User()
    user.newUp(userInJson)

    const posts = await user.posts().fetch()
    return posts.toJSON()
  }
},
Post: {
  // Fetch the author of a particular post
  async user(postInJson) {
    // Convert JSON to model instance
    const post = new Post()
    post.newUp(postInJson)

    const user = await post.user().fetch()
    return user.toJSON()
  }
}
Because we called toJSON() on our queries above, for us to be able to call a relationship or any other method on the models, we need to first convert the JSON back to an instance of the model. Then we can call our relationship methods (posts() and user()) defined earlier.
Finally, we export the resolvers:
// app/data/resolvers.js

module.exports = resolvers
Creating the GraphQL Server
We have successful build out each piece of our GraphQL server, let’s now put everything together. Open start/routes.js and update it as below:
// start/routes.js

'use strict'

const Route = use('Route')
const GraphqlAdonis = use('ApolloServer')
const schema = require('../app/data/schema');

Route.route('/graphql', ({ request, auth, response }) => {
    return GraphqlAdonis.graphql({
      schema,
      context: { auth }
    }, request, response)
}, ['GET', 'POST'])

Route.get('/graphiql', ({ request, response }) => {
    return GraphqlAdonis.graphiql({ endpointURL: '/graphql' }, request, response)
})
Firstly, we imported Route which we’ll use to define our routes. Then GraphqlAdonis (the adonis-apollo-server package) and lastly we imported our schema.
Since GraphQL can be served over HTTP GET and POST requests, we defined a /graphql route that accept both requests. Within this route, we call GraphqlAdonis’s graphql() passing to it an object containing our schema and auth object (as the context) as GraphQL options. The graphql() accepts 3 arguments. The first argument is the GraphQL options which is is an object. The remaining arguments are the request and response object respectively. We pass to the schema and auth object as the GraphQL options.
The /graphiql route is solely for testing out the GraphQL server. Though I won’t be using it for testing out the server in this tutorial. I only added it just to show you how to use GraphiQL with your server.
Testing Out the Server
I will be using Insomnia to test out the server. Insomnia is a REST client that has support for GraphQL query. You can download it if you don’t have it already.
Make sure the server is already started:
adonis serve --dev
Which should be running on http://127.0.0.1:3333.
Start the Insomnia app:
Click on create New Request. Give the request a name if you want, then select POST as the request method, then select GraphQL Query. Finally, click Create.
Next, enter http://127.0.0.1:3333/graphql in the address bar:
Try creating a new user with createUser mutation:
mutation {
    createUser(username: "mezie", email: "[email protected]", password: "password") {
        id
        username
        email
    }
}
You should get a response as in the image below:
Then login:
mutation {
    login(email: "[email protected]", password: "password")
}
A JWT is returned on successful login.
To test out adding a new posting, remember we said only authenticated users can add post? Though we are successfully logged in, but we need to find a way to add the JWT generated above to the request headers. Luckily for us, this is pretty simple with Insomnia (the major reason I chose to test out the GraphQL server with it).
From Auth dropdown, select Bearer Token and paste the token (JWT) above in the field provider.
With that added, you can now add a new post:
mutation {
    addPost(title: "Hello Adonis", content: "Adonis is awesome!") {
        id
        title
        slug
        content
        user {
            username
            email
        }
    }
}
Conclusion
There we have it! So, in this tutorial, we saw how to integrate Apollo server in an AdonisJS using the adonis-apollo-server package. We then went on to build a GraphQL server with it. We also saw how to add authentication to the GraphQL server using JSON Web Tokens (JWT). Finally, we saw how to test our GraphQL server using the Insomnia REST client.
So, go forth and build awesome GraphQL apps with AdonisJS.