How to build a GraphQL API with Node

#express #javascript #node

July 20, 2019
by Matteo Bertozzo

Find more about
[development]

GraphQL is the new popular topic in the web development world: everybody’s speaking about it and everybody wants to give it a try. It’s no more the new kid in the block and it is very easy to setup even in small projects, thanks to an extensive documentation.

Why do we need GraphQL?

GraphQL is new (or rather popular, its first public release was actually in 2015), but this reason alone isn’t enough to start using it. The real game changer is the fact that it is the solution to at least two problems with standard REST architectures:

  1. the strict mapping of resources to endpoints, that forces developers to think of each and every use case ahead of time
  2. the exchange of payloads containing data not needed by the UI, effectively wasting bandwidth

How does GraphQL solve these issues? Well, it introduces a query language that allows to decouple the UI from the services providing data. This way, the UI describes what data it needs, the backend what data it can provide and GraphQL matches the two.

Our goals

We’re going to build a backend running on Node, that will be able to receive and process GraphQL queries. Data will be stored and then retrieved from a MySQL database.

We’re building our app on top of [react-boilerplate]: even though not so beginner-friendly, it provides us a production-ready codebase and an already configured environment if in the future we’ll want to extend the project with a React frontend app to interact with the API .

Project setup and installation

To get started, head over your terminal and clone the react-boilerplate repository:

$ git clone --depth=1 https://github.com/react-boilerplate/react-boilerplate.git your-folder

Move inside the folder you’ve just created and run the setup:

$ cd your-folder
$ npm run setup

This will install the project dependencies and after a while will ask you if you want to initialize a new repository. Just press Enter to confirm and wait for the process to end. Once finished, run the following command to delete the demo app that comes together the boilerplate:

$ npm run clean

It’s now time to add the dependencies we’ll need in our project:

$ npm i mysql2 sequelize apollo-server-express apollo-boost graphql-tag graphql
$ npm i sequelize-cli faker -D

As you can see, there’s a bunch of stuff:

  • mysql2 is a client for the MySQL database
  • sequelize is an ORM, allowing us to write database queries using the object-oriented paradigm (in other words, we’ll be writing queries in Javascript instead of SQL)
  • apollo-server-express is the Express integration of Apollo Server (and Apollo Server is an open-source GraphQL server)
  • apollo-boost is a tool to quickly configure Apollo Client (that is a client to interact with the GraphQL server)
  • graphql-tag is an utility to parse Javascript template literal tag to actual GraphQL queries
  • graphql is the Javascript implementation for GraphQL

As dev-dependencies we have:

  • sequelize-cli allows us to run sequelize-specific commands from the terminal
  • faker is an utility to generate fake data (we’ll use it to quickly populate our DB)

Setting up the database

Time to move to the server folder and to create an api folder in it:

$ cd server
$ mkdir api
$ cd api

We’re going to add in here everything we need to setup our GraphQL api. First of all, let’s connect to the database and create all the tables we need for our project. Fortunately, sequelize helps us a lot, just type in the terminal the following commands:

$ ./../../node_modules/sequelize-cli/lib/sequelize init:models
$ ./../../node_modules/sequelize-cli/lib/sequelize init:config

If you’re on a Windows machine, the above commands may not be recognized. If that’s the case, you may want to install sequelize-cli globally with npm install -g sequelize-cli and simply run sequelize init:models and sequelize init:config

We now find in our api folder a bunch of stuff, let’s start from the config folder: in it you can find a config.json file where you can store the data needed for the connection to the database. You may notice that sequelize provides us three environments (development, test, production), as common in enterprise-level app production. In our case we’ll be using the development environment only, but if you plan to release your GraphQL app online, make sure to set (at least) the production environment variables too.

{
  "development": {
    "username": "your_username",
    "password": "your_password",
    "database": "your_database",
    "host": "127.0.0.1",
    "dialect": "mysql"
  }
}

It’s now time to decide what to store in our database: let’s suppose we want to save some posts in a table and the author who wrote each of them in a second table. In order to define the structure for each table we need to create models. A model is basically a representation of a table structure. Let’s start from the model for the post table: create a post.js file in the server/api/models/ folder and write in it what follows:

module.exports = (sequelize, DataTypes) => {
  const Post = sequelize.define('post', {
      id: {
        type: DataTypes.INTEGER,
        primaryKey: true,
        autoIncrement: true
      },
      title: DataTypes.STRING,
      content: {
        type: DataTypes.TEXT,
        allowNull: false
      },
    },
    {
      freezeTableName: true,
    }
  );
  Post.associate = (models) => {
    Post.belongsTo(models.author);
  };
  return Post;
}

Basically, we’re defining a table named post with three columns: id, title and content. We then used sequelize associate function, that allows us to specify a relationship between the post table and the author table. We need to define a complete one-to-many relationship between the author table and the post table: for what concerns the post table, we use the belongsTo sequelize function. The effect is that we’ll get a fourth column, authorId, storing ids acting as foreign keys.

Time now to define the author table. In the server/api/models/ folder create a new file with the name author.js and write in it what follows:

module.exports = (sequelize, DataTypes) => {
  const Author = sequelize.define('author', {
      id: {
        type: DataTypes.INTEGER,
        primaryKey: true,
        autoIncrement: true
      },
      firstName: DataTypes.STRING,
      lastName: DataTypes.STRING
    },
    {
      freezeTableName: true,
    }
  );
  Author.associate = (models) => {
    Author.hasMany(models.post);
  };
  return Author;
}

The author table definition is similar to the post table code, except here we used the hasMany function to complete the association definition, because each author can be linked to multiple posts.

If you want to know more about the database association we used above, you can look for the keywords one-to-many relationship or [click here] to go straight to the Wikipedia page.

Defining the GraphQL schema & resolvers

We now have to describe the functionalities available to the client that connects to our GraphQL server. To do so we need to define a schema. GraphQL allows us to define schemas with a syntax called SDL, or Schema Definition Language, that we can use to define types and the relations between them. The schema defines just the shape and the relationships for the data to return, not how or where to retrieve it. For this last purpose, we’ll define a resolver for each schema field.

Let’s begin with the schema.

In server/api/ create a new folder with the name gql, containing two empty files, schema.js and resolvers.js.

$ mkdir gql
$ cd gql
$ touch schema.js
$ touch resolvers.js

Open the schema.js file in your editor and write what follows:

module.exports = `
  type Author {
    id: ID!
    firstName: String!
    lastName: String!
    posts: [Post!]!
  }
  type Post {
    id: ID!
    title: String
    content: String!
    authorId: ID!
    author: Author!
  }
  type Query {
    posts: [Post!]!
    post(id: ID!): Post
    author(id: ID!): Author
    authors: [Author!]!
  }
  type Mutation {
    createPost(title: String, content:String!, authorId: ID!): Post!
    updatePost(id: ID!, title: String, content:String!): [Int!]!
    deletePost(id: ID!): Int!
  }
`;

Here we’re defining two object types, that are basically a group of fields. Each field maps another type, let’s take Author as example:

  • id is of type ID, intended as a unique identifier
  • firstName and lastName are of type String, UTF-8 encoded
  • posts is an array of Post objects

The exclamation mark means the field is non-nullable and GraphQL will always provide you a value when you query this field.

The last two types are Query and Mutation, and are reserved:

  • query is for fetching data and is similar to the GET verb for REST based APIs
  • mutation optionally defines actions to create, edit or delete data on the server, like the POST, PUT/PATCH, DELETE verbs in REST

As you can see, in our schema we have the following queries:

  • posts: retrive all available posts from the respective table and return them as an array of Post objects
  • post (id): get an ID as argument and return a Post object for the entry with the matching ID in the database
  • authors: retrieve all available authors from the respective table and return them as an array of Author objects
  • author (id): get an ID as argument end return the Author object for the entry with the matching ID in the database

The mutations are:

  • createPost: create an entry in the post table with the data provided as argument and return a Post object corresponding to the created entry
  • updatePost: edit the post corresponding to the provided ID with the data passed as argument
  • deletePost: remove from the DB the post corresponding to the provided ID

As we said before, we need to create the resolvers in order to actually define how to perform all these operations and with what data. To do so, just open in the editor the resolvers.js file we created before and write in it what follows:

module.exports = {
  Author: {
    posts: (obj, args, context, info) => obj.getPosts(),
  },
  Post: {
    author: (obj, args, context, info) => obj.getAuthor(),
  },
  Query: {
    posts: (obj, args, { db }, info) => db.post.findAll(),
    authors: (obj, args, { db }, info) => db.author.findAll(),
    post: (obj, { id }, { db }, info) => db.post.findByPk(id),
    author: (obj, { id }, { db }, info) => db.author.findByPk(id) 
  },
  Mutation: {
    createPost: (obj, { title, content, authorId }, { db }, info) =>
      db.post.create({
        title: title,
        content: content,
        authorId: authorId
      }),
    updatePost: (obj, { title, content, id }, { db }, info) =>
      db.post.update({
        title: title,
        content: content
      },
      {
        where: {
          id: id
        }
      }),
    deletePost: (obj, {id}, { db }, info) =>
      db.post.destroy({
        where: {
          id: id
        }
      })
  }
};

As you can see, each resolver accepts four arguments:

  • obj: object that contains the result of the resolver on the parent field, or the root value if on a top-level query
  • args: object with the arguments passed to the query
  • context: object shared by all resolvers in a query, we’ll define this when instantiating the GraphQL server
  • info: argument used only in advanced cases, holds informations about the execution state of the query

Sequelize here helps us a lot, since it provides out-of-the-box getter functions for all the fields we defined in our models. Particularly noteworthy are the getPosts() and the getAuthor() functions, generated by sequelize based on the relationship between the tables we specified earlier. Other resolvers use standard sequelize methods, like findAll(), findByPk(), create(), update() and destroy(). For further reference you can head over the [official documentation].

We’re almost done: just a last step to bind everything together and we’re ready to go!

Server setup and final touches…

The big moment is finally here: head over the server folder and open the index.js file in your editor. In there you’ll find the core configuration for our server, that we need to update in order to include the GraphQL functionalities.

First thing to do is to include some dependencies at the top of the file: add the following content after the const logger = require('./logger'); line.

const ApolloServer = require('apollo-server-express').ApolloServer;
const gql = require('apollo-server-express').gql;

const faker = require('faker');
const times = require('lodash').times;
const random = require('lodash').random;
const typeDefs = require('./api/gql/schema');

const resolvers = require('./api/gql/resolvers');
const db = require('./api/models');

Then, immediately before the line const app = express(); add the GraphQL server initialization:

const server = new ApolloServer({
  typeDefs: gql(typeDefs),
  resolvers,
  context: { db }
})

Here we’re passing to the Apollo Server instance the schema definition and the resolvers. As you can see, we are also passing a reference to our database as the context: this is the exact same context we’ve used earlier in our resolvers.

Now we have to actually connect Apollo Server to Express: after the const app = express(); line, add the following to make the /graphql endpoint available:

server.applyMiddleware({ app });

Almost done: the last touch is to actually sync our database with the models and populate the newly created tables with some fake data. To do so, change the last part of the index file, moving the app.listen(...) function inside the block that follows:

db.sequelize.sync({ force: true }).then(() => {
  db.author.bulkCreate(
    times(10, () => ({
      firstName: faker.name.firstName(),
      lastName: faker.name.lastName()
    }))
  );
  db.post.bulkCreate(
    times(10, () => ({
      title: faker.lorem.sentence(),
      content: faker.lorem.paragraph(),
      authorId: random(1, 10)
    }))
  );
  app.listen(port, host, async err => {
    if (err) {
      return logger.error(err.message);
    }
  
    // Connect to ngrok in dev mode
    if (ngrok) {
      let url;
      try {
        url = await ngrok.connect(port);
      } catch (e) {
        return logger.error(e);
      }
      logger.appStarted(port, prettyHost, url);
    } else {
      logger.appStarted(port, prettyHost);
    }
  });
});

Wrap up

We’re finally to the end of our journey, everything’s ready to be tested!

Fire up your console and move to your project root folder (if you’re not already in it), then type:

$ npm start

Wait for the server to start and then open your browser to http://localhost:3000/graphql. You’re now in the GraphQL Playground, a tool that allows you to create a query or mutation and run it against the server. A sample query can be the following:

query {
  posts {
    title
    content
  }
}

A sample mutation:

mutation {
  createPost(
    title:"Test",
    content:"Sample content for the test post"
    authorId:4
  ) {
    title
    content
    authorId
  }
}

And that’s it! We have a fully working GraphQL API, running on a Node server. Another article about creating a React app providing a simple UI to interact with this API may come in the future… stay tuned!