A simple introduction to GraphQL with ExpressJs and Docker

blankgodd

Damilare Agba

Posted on January 31, 2023

A simple introduction to GraphQL with ExpressJs and Docker

What is GraphQL

GraphQL is a specification for writing queries for APIs and a runtime for fulfilling those queries to already existing data. It is built around the HTTP protocol and defines how to send and receive resources from a server.

With GraphQL, you do not need to create multiple endpoints to request data like you would usually do when creating REST endpoints. You are provided with one singular endpoint which returns the required data based on the queries you send to the endpoint.

REST vs GraphQL

Imagine you are building a CMS (a content management system) and you have two tables in your database; user and posts. Below, you will see how both REST and GraphQL will handle a situation where you need to get all the posts for each user in your application.

REST

When using REST, you would first need to make a request to a first endpoint (e.g /users) which queries the user model to get a list of all the users in the db. After doing this, you can extract the id of each user before making another request to a second endpoint (e.g /users/posts) which returns all the posts created by that user. This means that you would have to make n number of calls to the /users/posts endpoint, n being the number of users in your application.

Also note that the response from the /users endpoint might contain other user information apart from the id like name, date of birth, location, age, etc which you do not really need in this context. This also applies to the /users/posts endpoint. This ultimately means that you get to make requests to endpoints more that you need to and that might slow down the application.

GraphQL

With GraphQL, you only need to create a single query which contains the exact data that you need. In this context, you tell it that you need all the users (their names only) and all posts created by each user (title only) by creating a query like the one below

query{
  users{
    name
    posts{
      title
    }
  }
}

Enter fullscreen mode Exit fullscreen mode

After creating the query, you send it to your GraphQL server which parses the query and returns the the appropriate data as defined in the query. Here, you get exactly what you need from a single endpoint, nothing more, nothing less. This greatly improves your application in many ways.

In a nutshell

In a nutshell, GraphQL gives us everything a REST API can do like CRUD functionalities, error handling, etc. It however improves on it by reducing the number of requests made, nesting data based on simple organized queries, which makes the whole server run faster and more optimized.

Express + GraphQL

Let us make a handy template for getting started with GraphQL applications. You'll first need to setup a simple Express application using GraphQL. You then add some functionalities to the application so you can see some of the features GraphQL has to offer by yourselves.

To get started, you create a basic express server by running the command below;

% npm init --yes
Enter fullscreen mode Exit fullscreen mode

This generates a package.json file which contains information about your application, and other information like the application dependencies and how to run it.

You need to install your application dependencies by running the commands below which adds them to your package.json file;

% npm i express express-graphql graphql
Enter fullscreen mode Exit fullscreen mode

These dependencies allows you to start up an express server, connect your expresses server to graphql (express-graphql) and also provides the GraphQL functionalities you need (graphql).

You'll also install nodemon to help restart your server when any file changes, and add a dev script to your package.json file.

% npm i --save-dev nodemon
Enter fullscreen mode Exit fullscreen mode

package.js

{ "scripts":
  {  "dev": "nodemon index.js"}
}
Enter fullscreen mode Exit fullscreen mode

PostgreSQL + Docker

After this is done, you'll set up a postgreSQL database using docker-compose and populate it with some users and posts so you can test your application. To do this, you'll create a docker-compose.yml file which will contain the configurations for the postgres service you'll be using. You can set that up by adding the folling lines to the docker-compose file

version: '3.8'
services:
  postgres:
    image: postgres:13-alpine
    ports:
      - 5432:5432
    env_file:
      - .env
    volumes:
      - postgres:/var/lib/postgresql/data
    networks:
      - graphql-express

volumes:
  postgres:
    name: graphql-express-docker-db

networks:
  graphql-express:

Enter fullscreen mode Exit fullscreen mode

Then go on to you terminal and run docker compose up -d or sudo docker-compose up -d (for linux). This spins up the postgres service and makes it available for your application. You also need to install sequelize, an ORM for connecting with and quering your database. To do the, install the following

% npm install sequelize pg pg-hstore
Enter fullscreen mode Exit fullscreen mode

Sequelize also provides a cli which makes it easier to user the ORM by providing cli commands. In this example, you'll make use of sequelize-cli to set up database connection and schema. Enter the following commands in your terminal

% npm install --save-dev sequelize-cli
...
% npx sequelize-cli init
Enter fullscreen mode Exit fullscreen mode

This will create a number of folders

  • config, which contains config file, which tells CLI how to connect with database
  • models, contains all models for your project
  • migrations, contains all migration files

After these has been created, you need to create your user and posts models. To do this, you can use the model:generate command which takes the model name and attributes

% npx sequelize-cli model:generate --name User --attributes name:string

% npx sequelize-cli model:generate --name Post --attributes title:string

Enter fullscreen mode Exit fullscreen mode

This generates the user and posts models which you can edit to add relationships. In the user model, you can add the following lines to the static associate function in the user.js file


static associate({ Post }) {
    this.hasMany(Post, { foreignKey: "userId", onDelete: "CASCADE" })
}

Enter fullscreen mode Exit fullscreen mode

and in the post.js file, you add the belongsTo association in the Post model


static associate({ Post }) {
    this.hasMany(Post, { foreignKey: "userId", onDelete: "CASCADE" })
}
Enter fullscreen mode Exit fullscreen mode

You can then run migration to persist the schema on the database using the db:migrate command


npx sequelize-cli db:migrate
Enter fullscreen mode Exit fullscreen mode

After this, you can create a seed file to insert some raw data by default. This will help you test the server later. You can create a seed file to create demo-users.

% npx sequelize-cli seed:generate --name demo-user
...
Enter fullscreen mode Exit fullscreen mode

You can then proceed to add demo users to the seed files. After adding these users you then use the db:seed:all command to create them.

xxxxxxxxxxxxx-demo-user.js

'use strict';

/** @type {import('sequelize-cli').Migration} */
module.exports = {
  async up (queryInterface, Sequelize) {
    return queryInterface.bulkInsert('Users', [{
        name: 'Naruto Uzumaki',
        createdAt: new Date(),
        updatedAt: new Date()
    }]);
  },
  async down (queryInterface, Sequelize) {
    return queryInterface.bulkDelete('Users', null, {});
  }
};


Enter fullscreen mode Exit fullscreen mode

then


% npx sequelize-cli db:seed:all
Enter fullscreen mode Exit fullscreen mode

Express Server

You can then set up a simple server by adding the following to the index.js file. This spins up the server and add GraphQL to it. It uses the graphqlHTTP function which takes two arguments, schema, which will be defined later and the graphiql which provides a GUI for interacting with our server.


const express = require('express');
const { graphqlHTTP } = require('express-graphql');
const { sequelize, User, Post} = require('./models');


const app = express();
app.use('/graphql', graphqlHTTP({
    schema: schema,
    graphiql: true
}))
app.listen(5000, () => (
    await sequelize.authenticate();
    console.log("Running")
))

Enter fullscreen mode Exit fullscreen mode

For this to work though, you need to define a schema which tells GraphQL how your data interacts with each other. This schema takes two arguments; the query argument which is a way for you to fetch data from the server and the mutation argument which gives your server other CRUD functionalities.


const schema = new GraphQLSchema({
    query: rootQuery,
    mutation: rootMutation
})

Enter fullscreen mode Exit fullscreen mode

To define your rootQuery, add the following lines to your server file. The rootQuery is a GraphQLObjectType which defines the name and description for the object. It servers as a data access layer (DAL) which defines the object fields which itself contains definitions of various query fields you can use to fetch data from your server. It also resolves those fields and returns the appropriate data by making queries to the the database.

// root query scope
const rootQuery = new GraphQLObjectType({
    name: "Query",
    description: "Root Query",
    fields: () => ({
        posts: {
            type: new GraphQLList(PostType),
            description: 'List of posts',
            resolve: () => Post.findAll()
        },
        users: {
            type: new GraphQLList(UserType),
            description: 'List of users',
            resolve: () => User.findAll()
        },
        post: {
            type: PostType,
            description: 'A single post',
            args: {
                id: { type: GraphQLInt }
            },
            resolve: (parent, args) => Post.findOne({
                where: {
                    id: args.id
                }
            })
        },
        user: {
            type: UserType,
            description: 'A single user',
            args: {
                id: { type: GraphQLInt }
            },
            resolve: (parent, args) => User.findOne({
                where: {
                    id: args.id
                }
            })
        }
    })
})

Enter fullscreen mode Exit fullscreen mode

You can also define your rootMutation which is very similar to the the rootQuery object. However it differs because it is used to mutate or change data on your server.



const rootMutation = new GraphQLObjectType({
    name: "Mutation",
    description: "Root Mutation",
    fields: () => ({
        addUser: {
            type: UserType,
            description: 'Add a user',
            args: {
                name: { type: GraphQLNonNull(GraphQLString) }
            },
            resolve: (parent, args) => {
                const user = User.create({
                    name: args.name
                })
                return user
            } 
        },
        addPost: {
            type: PostType,
            description: 'Add a post',
            args: {
                title: { type: GraphQLNonNull(GraphQLString) },
                userId: { type: GraphQLNonNull(GraphQLInt) }
            },
            resolve: (parent, args) => {
                const post = Post.create({
                    title: args.title,
                    userId: args.userId
                })
                return post
            }
        }
    })
})
Enter fullscreen mode Exit fullscreen mode

Both the rootQuery and the rootMutation defines some types like the PostType and UserType which represents the definitions for your posts and users respectively. To define these types, which are a bit similar with the rootQuery and rootMutation, you should input the following and edit as required

const UserType = new GraphQLObjectType({
    name: "User",
    description: "A User in our application",
    fields: () => ({
        id: { type: GraphQLNonNull(GraphQLInt) },
        name: { type: GraphQLNonNull(GraphQLString) },
        posts: {
            type: new GraphQLList(PostType),
            resolve: (user) => Post.findAll({
                where: {
                    userId: user.id
                }
            })
        }
    })
})

const PostType = new GraphQLObjectType({
    name: "Post",
    description: "A Post created by a user",
    fields: () => ({
        id: { type: GraphQLNonNull(GraphQLInt) },
        title: { type: GraphQLNonNull(GraphQLString) },
        userId: { type: GraphQLNonNull(GraphQLInt) },
        user: {
            type: UserType,
            resolve: (post) => User.findOne({
                where: {
                    id: post.userId
                }
            })
        }
    })
})

Enter fullscreen mode Exit fullscreen mode

Testing the server

With your database running and everything above put in place as you require, you can proceed to run npm run dev in your terminal session to spin up the server. Your expres server should be available on localhost:5000 and you can interact with the GraphQL server on localhost:5000/graphql which provides a nice user interface.

With this, you have successfully created a GraphQL server using express and connected to a PostgreSQL database instance that was created using docker compose.

The full code for this article is available here on github.

Cheers.

💖 💪 🙅 🚩
blankgodd
Damilare Agba

Posted on January 31, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related