GraphQL is the new buzzword in API development. While RESTful APIs remain the most popular way to expose data from applications, they come with many limitations that GraphQL aims to solve.

GraphQL is a query language created by Facebook, which was turned into an open-source project in 2015. It offers an intuitive and flexible syntax for describing and accessing data in an API.

This guide will explore how to build a GraphQL Node.js project. We’ll use GraphQL to build a Todo application in the Express.js web framework for Node.

What Is GraphQL?

From the official documentation: “GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more, makes it easier to evolve APIs over time, and enables powerful developer tools.”

GraphQL is a server-side runtime for executing queries using the type system you defined for your data. Also, GraphQL is not tied to any specific database or storage engine. Instead, it is backed by your existing code and data store. You can get a detailed comparison of these technologies with the GraphQL vs. RESTful API guide.

To create a GraphQL service, you start by defining schema types and creating fields using those types. Next, you provide a function resolver to be executed on each field and type whenever data is requested by the client side.

GraphQL Terminology

GraphQL type system is used to describe what data can be queried and what data you can manipulate. It is the core of GraphQL. Let’s discuss different ways we can describe and manipulate data in GraphQL.

Object Types

GraphQL object types are data models containing strongly typed fields. There should be a 1-to-1 mapping between your models and GraphQL types. Below is an example of GraphQL Type:

type User {
  id: ID! # The "!" means required
  firstname: String
  lastname: String
  email: String
  username: String
  todos: [Todo] # Todo is another GraphQL type
}

Queries

GraphQL Query defines all the queries that a client can run on the GraphQL API. You should define a RootQuery that will contain all existing queries by convention.

Below we define and map the queries to the corresponding RESTful API:

type RootQuery {
  user(id: ID): User           # Corresponds to GET /api/users/:id
  users: [User]                # Corresponds to GET /api/users
  todo(id: ID!): Todo    # Corresponds to GET /api/todos/:id
  todos: [Todo]          # Corresponds to GET /api/todos
}

Mutations

If GraphQL Queries are GET requests, mutations are POST, PUT, PATCH, and DELETE requests that manipulate GraphQL API.

We will put all the mutations in a single RootMutation to demonstrate:

type RootMutation {
  createUser(input: UserInput!): User             # Corresponds to POST /api/users
  updateUser(id: ID!, input: UserInput!): User    # Corresponds to PATCH /api/users
  removeUser(id: ID!): User                       # Corresponds to DELETE /api/users
  createTodo(input: TodoInput!): Todo
  updateTodo(id: ID!, input: TodoInput!): Todo
  removeTodo(id: ID!): Todo
}

You noticed the use of -input types for the mutations such as UserInput, TodoInput. It is always best practice to always define Input types for creating and updating your resources.

You can define the input types like the one below:

input UserInput {
  firstname: String!
  lastname: String
  email: String!
  username: String!
}

Resolvers

Resolvers tell GraphQL what to do when each query or mutation is requested. It is a basic function that does the hard work of hitting the database layer to do the CRUD (create, read, update, delete) operations, hitting an internal RESTful API endpoint, or calling a microservice to fulfill the client’s request.

You can create a new resolvers.js file and add the following code:

import sequelize from '../models';
export default function resolvers () {
  const models = sequelize.models;
  return {
    // Resolvers for Queries
    RootQuery: {
      user (root, { id }, context) {
        return models.User.findById(id, context);
      },
      users (root, args, context) {
        return models.User.findAll({}, context);
      }
    },
    User: {
      todos (user) {
        return user.getTodos();
      }
    },
  }
  // Resolvers for Mutations
  RootMutation: {
    createUser (root, { input }, context) {
      return models.User.create(input, context);    
    },
    updateUser (root, { id, input }, context) {
      return models.User.update(input, { ...context, where: { id } });
    },
    removeUser (root, { id }, context) {
      return models.User.destroy(input, { ...context, where: { id } });
    },
    // ... Resolvers for Todos go here
  }
}

Schema

GraphQL schema is what GraphQL exposes to the world. Therefore, the types, queries, and mutations will be included inside the schema to be exposed to the world.

Below is how to expose types, queries, and mutations to the world:

schema {
  query: RootQuery
  mutation: RootMutation
}

In the above script, we included the RootQuery and RootMutation we created earlier to be exposed to the world.

How Does GraphQL Work With Node.js and Express.js?

GraphQL provides an implementation for all major programming languages, and Node.js is not exempted. On the official GraphQL website, there is a section for JavaScript support, and also, there are other implementations of GraphQL to make writing and coding simple.

GraphQL Apollo provides an implementation for Node.js and Express.js and makes it easy to get started with GraphQL.

You will learn how to create and develop your first GraphQL application in Node.js and Express.js backend framework using GraphQL Apollo in the next section.

Setting up GraphQL With Express.js

Building a GraphQL API server with Express.js is straightforward to get started. In this section, we will explore how to build a GraphQL server.

Initialize Project With Express

First, you need to install and set up a new Express.js project. Create a folder for your project and install Express.js using this command:

cd <project-name> && npm init -y
npm install express

The command above creates a new package.json file and installs the Express.js library into your project.

Next, we will structure our project as shown in the image below. It will contain different modules for the features of the project, such as users, todos, etc.

A list of files in graphql-todo.
Files for graphql-todo.

Initialize GraphQL

Let’s start by installing the GraphQL Express.js dependencies. Run the following command to install:

npm install apollo-server-express graphql @graphql-tools/schema --save

Creating Schemas and Types

Next, we are going to create an index.js file inside the modules folder and add the following code snippet:

const { gql } = require('apollo-server-express');
const users = require('./users');
const todos = require('./todos');
const { GraphQLScalarType } = require('graphql');
const { makeExecutableSchema } = require('@graphql-tools/schema');
const typeDefs = gql`
  scalar Time
  type Query {
    getVersion: String!
  }
  type Mutation {
    version: String!
  }
`;
const timeScalar = new GraphQLScalarType({
  name: 'Time',
  description: 'Time custom scalar type',
 serialize: (value) => value,
});
const resolvers = {
  Time: timeScalar,
  Query: {
    getVersion: () => `v1`,
  },
};
const schema = makeExecutableSchema({
  typeDefs: [typeDefs, users.typeDefs, todos.typeDefs],
  resolvers: [resolvers, users.resolvers, todos.resolvers],
});
module.exports = schema;

Code Walkthrough

Let’s work through the code snippet and break it down:

Step 1

First, we imported the required libraries and created default query and mutation types. The query and mutation only set the version of the GraphQL API for now. However, we will extend the query and mutation to include other schemas as we proceed.

A command line interface showing the
Importing GraphQL and extensions.
Step 2:

Then we created a new scalar type for time and our first resolver for the query and mutation created above. In addition, we also generated a schema using the makeExecutableSchema function.

The generated schema includes all the other schemas we imported and will also include more when we create and import them.

A command line interface showing the
Creating a scalar type for time as well as our first resolver.

The above code snippet shows that we imported different schemas into the makeExecutableSchema function. This approach helps us in structuring the application for complexity. Next, we are going to create the Todo and User schemas we imported.

Creating Todo Schema

The Todo schema shows simple CRUD operations that users of the application can perform. Below is the schema that implements the Todo CRUD operation.

const { gql } = require('apollo-server-express');
const createTodo = require('./mutations/create-todo');
const updateTodo = require('./mutations/update-todo');
const removeTodo = require('./mutations/delete-todo');
const todo = require('./queries/todo');
const todos = require('./queries/todos');
const typeDefs = gql`
  type Todo {
    id: ID!
    title: String
    description: String
    user: User
  }
  input CreateTodoInput {
    title: String!
    description: String
    isCompleted: Boolean
  }
  input UpdateTodoInput {
    title: String
    description: String
    isCompleted: Boolean
  }  extend type Query {
    todo(id: ID): Todo!
    todos: [Todo!]
  }
  extend type Mutation {
    createTodo(input: CreateTodoInput!): Todo
    updateTodo(id: ID!, input: UpdateTodoInput!): Todo
    removeTodo(id: ID!): Todo
  }
`;
// Provide resolver functions for your schema fields
const resolvers = {
  // Resolvers for Queries
  Query: {
    todo,
    todos,
  },
  // Resolvers for Mutations
  Mutation: {
    createTodo,
    updateTodo,
    removeTodo,
  },
};
module.exports = { typeDefs, resolvers };

Code Walkthrough

Let’s work through the code snippet and break it down:

Step 1:

First, we created a schema for our Todo using GraphQL type, input, and extend. The extend keyword is used to inherit and add new queries and mutations to the existing root query and mutation we created above.

A command line interface showing the schema for our Todo script, including new inputs.
Creating the schema for our Todo.
Step 2:

Next, we created a resolver, which is used to retrieve the correct data when a particular query or mutation is called.

A command line interface showing the code to create a resolver for our Todo.
Creating a resolver.

With the resolver function in place, we can create individual methods for the business logic and database manipulation as shown in the create-todo.js example.

Create a create-user.js file in the ./mutations/ folder and add the business logic to create a new Todo in your database.

const models = require('../../../models');
module.exports = async (root, { input }, context) => {
  return models.todos.push({ ...input });
};

The code snippet above is a simplified way of creating a new Todo in our database using the Sequelize ORM. You can learn more about Sequelize and how to set it up with Node.js.

You can follow the same step to create many schemas depending on your application or you can clone the complete project from GitHub.

Next, we are going to set up the server with Express.js and run the newly created Todo application with GraphQL and Node.js.

Setting up and Running the Server

Lastly, we will set up our server using the apollo-server-express library we install earlier and configure it.

The apollo-server-express is a simple wrapper of Apollo Server for Express.js, It’s recommended because it has been developed to fit in Express.js development.

Using the examples we discussed above, let’s configure the Express.js server to work with the newly installed apollo-server-express.

Create a server.js file in the root directory and paste in the following code:


const express = require('express');
const { ApolloServer } = require('apollo-server-express');
const schema = require('./modules');
const app = express();
async function startServer() {
  const server = new ApolloServer({ schema });
  await server.start();
  server.applyMiddleware({ app });
}
startServer();
app.listen({ port: 3000 }, () =>
  console.log(`Server ready at http://localhost:3000`)
);

In the code above, you have successfully created your first CRUD GraphQL server for Todos and Users. You can start your development server and access the playground using http://localhost:3000/graphql. If everything is successful, you should be presented with the screen below:

A development interface showing a simple query in response.
The verification screen.

Summary

GraphQL is modern technology supported by Facebook that simplifies the tedious work involved in creating large-scale APIs with RESTful architectural patterns.

This guide has elucidated GraphQL and demonstrated how to develop your first GraphQL API with Express.js.

Let us know what you build using GraphQL in the comments below.

Solomon Eseme

I am a Software Engineer and Content Creator who is geared toward building high-performing and innovative products following best practices and industry standards. I also love writing about it at Masteringbackend.com. Follow me on Twitter, LinkedIn, and About Me