DEV Community

This Dot Media for This Dot

Posted on • Updated on • Originally published at labs.thisdot.co

GraphQL is the new REST — Part 1

GraphQL is a new API standard and offers a revolutionary approach to building data-driven applications. The project was first created by Facebook while they were shifting their mobile app from HTML5 to native mobile app.

Only In 2015 did GraphQL open to the public as an open-source project.

The major features and benefits of GraphQL urge you to rethink the way you build your clients’ apps and the way they communicate with backend servers to query or mutate data. For instance, GraphQL is:

  • A powerful query language used to communicate data between a client browser and a server.

  • An application-level query language rather than a database query language.

  • Platform-agnostic, whether on the server-side or client-side, GraphQL can embed itself seamlessly with many programming languages, provided an integration is in place (C#, Go, Java, Python, Node.js, etc.).

  • Database-agnostic, enabling you to connect GraphQL to any database of choice by providing the required hooks by GraphQL.

  • Dedicated to a declarative data fetching approach. Within a GraphQL query, you define exactly what data or fields you are querying for and what input filters you are sending. You compose a query from objects and sub-objects as per your needs.

  • An alternative to RESTful and a more flexible approach to manage data in your apps.

RESTful approach vs GraphQL approach

Let’s say that, in our app, we are tracking data about Books and Authors. With RESTful, you would define a REST API and expose multiple endpoints that a client app could communicate with.

For instance, you would define the following API endpoints:

  • /my-domain/Books — GET all books

  • /my-domain/Books/1 — GET book with ID = 1

  • /my-domain/Authors — GET all authors

  • /my-domain/Authors/1 — GET author with ID = 1

The cycle starts by requesting all books (/Books) in the library. With a book, you receive the Author ID. To get details about the author, you issue a new request to the REST API on a different endpoint (/Authors/1) to get the Author. With an author, you receive list of Book IDs written by this author. To get the details for a certain book, you issue a new request to the REST API on a different endpoint (/Books/1).

With REStful, the app is in continuous communication mode with the server to query the data and traverse it.

GraphQL is your saving grace. The above communication between the client and the server can be summarized with a single GraphQL query.

query {
    books {
        id
        name
        genre
        author {
            id
            name
            age
            books {
                id
                name
            }
        }
    }
}

The GraphQL standard allows you to build a graph of related data in the most efficient and concise way.

With a single query, you retrieve information about the books, the author of each book and all the books authored by that author.

In this series of articles (Part 1 and Part 2, we will build together a GraphQL Server API and a GraphQL Client app to communicate in a GraphQLful way.

Learning by example

The best way to learn GraphQL syntax and concepts is to build your own GraphQL Server API and Client app. I will take you through the process of building a GraphQL Server API on top of Node.js and Express engine. At the same time, I will be using Angular 7 to build a client app to utilize the GraphQL Server API and perform CRUD operations for managing Books and Authors.

In Part 1 of this article, we will build the GraphQL Server API. In Part 2, we will build the GraphQL Angular Client app.

You can find the source code for this article on Github by following this link GraphQL CRUD repo.

Build the GraphQL Server API

The GraphQL API Server will be a Node.js application. Follow these steps to get your GraphQL server up and running.

Create a wrapping folder for the application

Start by creating a new folder named graphql-crud. This folder will hold the source code for both the client and the server.

Add the main JavaScript file for the Node.js app

Add a new folder named server inside the graphql-crud folder. Inside this new folder, create a new JS file named app.js. This file is the main Node.js file that will start the whole server-side application.

Let’s now add some NPM packages that we will necessary when building the server. Issue the following command:

npm install express express-graphql graphql mongoose cors — save

express: Enables the Node.js app to listen to requests and server responses.

express-graphql: Enables the Node.js app to understand and process GraphQL requests/responses.

graphql: The JavaScript reference implementation for GraphQL.

mongoose: An ORM to use MongoDB inside a Node.js app. We will be storing our data inside a MongoDB instance using mLab free and online service.

cors: A Node.js package for providing a Connect/Express middleware that can be used to enable CORS with various options.

Require the libraries needed into app.js file

Require the following libraries at the beginning of app.js file:

const express = require('express');

// Import GraphQL middleware
const expressGraphQL = require('express-graphql');

// Import Mongo Db Client API
const mongoose = require('mongoose');

// Import CORs middleware to allow connections from another URL:PORT
const cors = require('cors');

Create the App instance and connect to MongoDB instance

To create a Node.js app instance:

const app = express();

To enable CORS on the Node.js app instance:

app.use(cors());

To connect to a MongoDB instance:

mongoose.connect(‘{CONNETION_STRING}’);

Wondering how to obtain a connection string to a MongoDB instance? If you are following this article and using mLab online service, I suggest you create an account for yourself on mLab and then create a new MongoDB instance. Don’t forget to create a database user and password for the newly created database instance. Once a username and password are created, mLab provide you with a connection string that you grab and put in the line of code above.

Finally, configure the Node.js app to listen on Port 4000 for incoming requests.

app.listen(4000, () => {
    console.log('Listening for requests on port 4000');
});

Add GraphQL middleware

Express allows external APIs or packages to hook into the request/response model by means of a middleware. To redirect all requests coming on /graphql to the GraphQL middleware, configure the Node.js app with the following: (Add the code just above the app.listen())

app.use('/graphql', expressGraphQL({
    schema,
    graphiql: true
}));

Any request to /graphql is now handled by expressGraphQL middleware. This middleware requires passing as a parameter the schema to be used by the GraphQL Server API. We will define this schema soon below. The graphiql: true option enables an in-browser IDE to run your queries and mutations against the GraphQL Server. This is very helpful when testing your GraphQL Server API before having an actual client app connected to the server.

GraphQL Schema

GraphQL flexibility is governed by the flexibility your API provides in terms of which object types can be queried or mutated and which fields belonging to the object types that can be returned by the API and consumed by the clients. This all depends on the schema and object types that you build and configure with the GraphQL Server. At the end of the day, GraphQL will validate a query or mutation based on a set of rules that the developer of the API has provided depending on a certain schema. This is the real power of GraphQL.

A GraphQL Schema is more or less analogous to a RESTful API route tree. Defining a schema means:

  • You tell the GraphQL Server about the object types to expect, whether in the body of the queries/mutations or in the response generated by those queries/mutations.

  • You build the valid “Endpoints” that the GraphQL Server exposes to client applications.

Build a GraphQL Schema

Create a new JavaScript file schema.js inside a new folder named schema. Import the GraphQL library at the top of the file as follows:

const graphQL = require(‘graphql’);

Next, import some of the GraphQL types to be used later throughout the code:

const {
  GraphQLObjectType,
  GraphQLString,
  GraphQLSchema,
  GraphQLID,
  GraphQLInt,
  GraphQLList,
  GraphQLNonNull
} = graphQL;

We are using the ES6 destructuring feature to extract the types from graphql-js package. We will be using those types to define our schema below.

A schema is defined by query and mutation.

A query is a root endpoint exposing all available query API endpoints to the client apps.

A mutation is a root endpoint exposing all available mutation (update, create or delete) API endpoints to the client apps.

module.exports = new GraphQLSchema({
  query: RootQuery,
  mutation: Mutation
});

We define the RootQuery as follows:

const RootQuery = new GraphQLObjectType({
  name: 'RootQueryType',
  fields: {
    book: {
      // book() {} endpoint
      type: BookType,
      args: {
        id: {
          type: GraphQLID
        }
      },
      resolve(parent, args) {
        // mongoose api call to return a book by ID
      }
    },
    author: {
      // author() {} endpoint
      type: AuthorType,
      args: {
        id: {
          type: GraphQLID
        }
      },
      resolve(parent, args) {
        // mongoose api call to return the book author
      }
    },
    books: {
      // books {} endpoint
      type: new GraphQLList(BookType),
      resolve(parent, args) {
        // mongoose api call to return all books
      }
    },
    authors: {
      // authors {} endpoint
      type: new GraphQLList(AuthorType),
      resolve(parent, args) {
        // mongoose api call to return authors
      }
    }
  }
});

The RootQuery object is of type GraphQLObjectType. It has a name of RootQueryType and an array of fields. The array of fields is actually an array of endpoints that the client apps can use to query the GraphQL Server API.

Let’s explore one of the endpoints. The rest will be similar.

book: {
      // book() {} endpoint
      type: BookType,
      args: {
        id: {
          type: GraphQLID
        }
      },
      resolve(parent, args) {
        // mongoose api call to return a book by ID
      }
    }

To define an endpoint, you provide the following fields:

type: The data that this endpoint returns. In this case, it is the BookType. We will get back to this type shortly.

args: The arguments that this endpoint accepts to filter the data accordingly and return a response. In this case, the code defines a single argument named id and is of type GraphQLID. This is a special type provided by GraphQL to indicate that this field is an ID of the object rather than a normal field.

resolve: A function that gets called by GraphQL whenever it is executing the book query endpoint. This function should be implemented by the developer to return a Book type based on the id argument passed to this query. We will fill in the gaps later on when we connect to a MongoDB instance to retrieve a book based on its id. The resolve() function receives as arguments the parent object (if any) and the list of arguments packaged under the args input parameter. Therefore, to access the book id passed to this query, you do it by args.id.

That’s the power of GraphQL. It allows you, the author, to build a graph of objects with instructions in the form of resolve() functions, to tell GraphQL how to query for a sub-object by executing the resolve() function, and so on.

The BookType is defined as follows:

const BookType = new GraphQLObjectType({
  name: 'Book',
  fields: () => ({
    // Fields exposed via query
    id: {
      type: GraphQLID
    },
    name: {
      type: GraphQLString
    },
    genre: {
      type: GraphQLString
    },
    author: {
      // How to retrieve Author on a Book object
      type: AuthorType,
      /**
       * parent is the book object retrieved from the query below
       */
      resolve(parent, args) {
        // mongoose api call to return the book author by authorId
      }
    }
  })
});

Define an object type by using GraphQLObjectType. A type is defined by providing the name Book in our case. In addition, you return an object with all the available fields on the book type inside the fields() function. The fields defined on the object type are the only fields available for a query response.

The Book type in our case defines the following fields:

  • An id field of type GraphQLID

  • A name field of type GraphQLString

  • A genre field of type GraphQLString

  • An author field of type AuthorType. AuthorType is another custom object type that we define ourselves. This field is special because it defines a resolve() function. This function receives two input parameters: parent and args. The parent parameter is the actual Book object queries by GraphQL. When GraphQL wants to return a field of type custom object type, it will execute the resolve() function to return this object. Later on, we will add the mongoose API call to retrieve an author from the database by means of Book.authorId field. This is the beauty of GraphQL in giving the developer of the API the upper hand to define the different pathways that GraphQL can use to traverse the object model and return a single response per query, no matter how complicated the query is.

Let’s define the Mutation object used by the schema.

const Mutation = new GraphQLObjectType({
  name: 'Mutation',
  fields: {
    addBook: {
      // addBook() {} endpoint
      type: BookType,
      args: {
        name: {
          type: new GraphQLNonNull(GraphQLString)
        },
        genre: {
          type: new GraphQLNonNull(GraphQLString)
        },
        authorId: {
          type: new GraphQLNonNull(GraphQLID)
        }
      },
      resolve(parent, args) {
        // mongoose api call to insert a new book object and return it as a reponse
      }
    },
  }
});

Similar to the RootQuery object, the Mutation object defines the API endpoints that can be used by client apps to mutate or change the data. We will go through one of the mutations here, and the rest are available on the Github repo.

The code fragment defines the addBook endpoint. This endpoint returns data of type BookType. It receives name, genre and authorId as input parameters. Notice how we can make input parameters mandatory by using the GraphQLNonNull object. In addition, this mutation defines the resolve() function. This function is executed by GraphQL when this endpoint is called. The resolve() function should contain code to insert or add a new Book object to the MongoDB.

Now that the GraphQL is defined, let’s explore an example of how to connect to MongoDB using the mongoose client API.

Connect to MongoDB via mongoose API

Let’s start by defining the schema of the database that mongoose will connect to. To do so, create a new folder named models. Then, for each collection or table in the relational database terms, you add a JavaScript file and define the mongoose model object.

For now, add the book.js file with the following code:

const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const bookSchema = new Schema({
   /**
   * No need to add "id" column
   * It is being created by mLab as "_id"
   */
   name: String,
   genre: String,
   authorId: String
});
module.exports = mongoose.model('Book', bookSchema);

This is the mongoose way of defining a collection inside MongoDB. This collection will be created inside the MongoDB database the first time it connects to the database instance and doesn’t find a collection there.

The Book collection in our case defines the name, genre, and authorId fields.

Let’s import this collection to our schema.js file and connect to MongoDB. The rest of the collections are available on the Github repo.

Start by requiring or importing the Book collection as follows:

const Book = require(‘../models/book’);

Now let’s revisit the book query endpoint and implement its resolve() function to return an actual book from the database:

book: {
      // book() {} endpoint
      type: BookType,
      args: {
        id: {
          type: GraphQLID
        }
      },
      resolve(parent, args) {
        return Book.findById(args.id);
      }
    }

Simply by calling the findById() function on the Book model, you are retrieving a Book object based on its id field.

The rest of the mongoose API calls are available on the Github repo.

Now that the GraphQL schema is ready and we’ve already configured the Express-GraphQL middleware with this schema, it’s showtime!

Demonstrate the Graphiql IDE

Run the server app by issuing the following command:

node app.js

The server is now up and running on PORT 4000. Let’s open a browser and visit this URL: http://localhost:4000/graphiql

On the left side you can type in your queries or mutations.

In the middle is the results panel, where the results of a query or mutation are displayed.

On the right side you find the Documentation Explorer. You can use it to diagnose the documentation of the GraphQL Server API. Here you are able to see the available queries and mutations, together with their details, as well as how to construct the queries and which fields are available to you.

Let’s add a single book by typing the following GraphQL query. Check the GraphQL website for a complete reference on how to construct queries and mutations.

mutation addBook($name: String!, $genre: String!, $authorId: ID!) {
    addBook(name: $name, genre: $genre, authorId: $authorId) {
        id
        name
    } 
}

In the Query Variables section of the panel, on the left side, add the following variables:

{
  "name":"Learning GraphQL",
  "genre": "APIs", 
  "authorId": "cjktxh5i50w53yul6lp4n"
}

The mutation above creates a new Book record with a certain name, genre, and authorId and returns the id and name of the newly created Book.

The response generated by the above mutation is as follows:

{
  id: ecjkv56ab219vireudxw
  name: Learning GraphQL
}

To query this book, you issue the following:

query {
    book (id: ecjkv56ab219vireudxw) {
        Name
        Genre
    }
}

Now, the GraphQL Server API is up and running. Let’s switch gears and build the GraphQL Angular Client app in Part 2 of this series.

Conclusion

GraphQL is considered a recent addition to technology. With its rapid momentum, it looks like it will replace the REStful in terms of accessing and manipulating data in our applications.

In this first part of the series on GraphQL, we’ve built together a GraphQL Server API on top of Node.js Express. In Part 2 of this series, we will build a GraphQL Client app with Angular.

Stay tuned!

This post was written by Bilal Haidar, a mentor with This Dot.

Need JavaScript consulting, mentoring, or training help? Check out our list of services at This Dot Labs.

Top comments (0)