Fast GraphQL Execution with Query Caching Prepared Statements

Fast GraphQL Execution with Query Caching Prepared Statements

Hasura GraphQL Engine is fast and there are different dimensions to it; latency, throughput, concurrency and so on.

Hasura GraphQL Engine is fast and there are different dimensions to it; latency, throughput, concurrency and so on.

In this post, we will look at important performance considerations for building apps at scale and how we leveraged PostgreSQL query caching and prepared statements to improve performance.

Performance Metrics

High Throughput

Hasura can process a large number of queries (1000 q/sec) in a tiny footprint of just 50MB RAM and importantly with low latency.

Concurrency & Realtime

Hasura supports a massive amount of concurrency, particularly useful for real time applications. For example, we have tested this out by benchmarking our subscriptions; 1 million subscriptions dispatching unique updates to 1 million connected clients every second.

Large queries & results

Despite being a layer on top of Postgres, for fairly large queries and (or) large results, Hasura has been able to match performance within 1% of native Postgres. We will look into detail how this was made possible below.

Hasura GraphQL query processing architecture

There are multiple stages to processing the incoming GraphQL query. Hasura does query parsing, then validates it against the Authorization engine with corresponding session variables. Then there is a Planner, which understands how to convert the GraphQL query to a SQL query.

This forms the Data Wrapper interface which will act as the base for transforming a GraphQL query into SQL, NoSQL or any other query interface as long as there is type information. We will look at how a GraphQL query is processed in the Data Wrapper.

Compiling GraphQL to SQL

Typically when you think of GraphQL servers processing a query, you think of the number of resolvers involved in fetching the data for the query. This approach can lead to multiple hits to the database with obvious constraints associated with it. Batching with data loader can improve the situation by reducing the number of calls.

Internally Hasura parses a GraphQL query, gets an internal representation of the GraphQL AST. This is then converted to a SQL AST and with necessary transformations and variables the final SQL is formed.

GraphQL Parser -> GraphQL AST -> SQL AST -> SQL

This compiler based approach allows Hasura to form a single SQL query for a GraphQL query of any depth.

PostgreSQL Prepared Statements

The SQL Statement (that was compiled in the step above) must be parsed. But imagine repeating the same or very similar requests frequently and Postgres needing to parse it, consuming time that could have been spent somewhere else. This is where Postgres prepare statements come in, where parsing can be skipped. Only the planning and execution will happen. The prepare statements are session specific and work only for that session.

This is how Postgres handles a SQL statement normally.

SQL query -> Plan, Optimise, Execute.

In prepared statements scenario,

(SQL query, name) -> Execute

Consider this example:

PREPARE fetchArticles AS
SELECT id, title, content FROM article WHERE id = $1;

and you execute this using the following

EXECUTE fetchArticles(‘f1e8aa91’);

The id parameter can change or the same could be repeated as long as it’s in the same postgres session.

GraphQL Authorization with prepare

However, it's not enough to just translate GraphQL to SQL! When queried by users or apps or clients, a GraphQL API should make sure that the data access is secure. This is the most common form of "business logic" that is embedded in a hand-written GraphQL server. Hasura automates this by providing developers with a fine-grained declarative auth DSL for every Postgres entity.

In the context of Authorization, Hasura supports adding session variables in headers (x-hasura-*). In many cases you would want to restrict data fetching based on the user who is logged in. Ex: fetch articles written by the currently logged in user.

With Hasura, you define a permission policy that implies these conditions. When the GraphQL Query is made, the session variables corresponding to the permission policy are injected into the where clause arguments.

PREPARE fetchArticlesByAuthor AS
SELECT id, title, content FROM article WHERE id= $1 AND author_id = $2;
Now the above statement can be executed like below:

EXECUTE fetchArticlesByAuthor(‘f1e8aa91’, 1);

Note that the author id coming from the session variable has been passed in as an argument to the WHERE clause. The value of the argument could change depending on the user who is logged in. But with prepared statements, the parsing of the query is avoided by the database.

Obviously the above example is the simplest of cases. But Hasura’s authorization system has to support the following use cases:

  • Auth per graphql type, using filtering conditions or property of the session
  • The filtering conditions can traverse relationships to arbitrary depths.
  • Multiple roles for the same query
  • Schema visibility (field level access)

This we believe increases performance vastly and our benchmarks do represent the same. Read more about Tweaking GraphQL Performance using Postgres Explain Command for optimising the generated SQL. Some quick fixes like adding the right indexes would boost the performance a lot.

PostgreSQL JSON Aggregations

Efficiently generating the SQL is one part of the optimisation. But now how do we parse the response from the database which is a flat table into a neat JSON that the client can understand?

Doing transformations of SQL results into client readable JSON would mean double processing since the database already did one level of processing to generate a response and now before sending it back to the client, there’s another round of data transformation. The larger the data, the more time it is to send it back to the client.

This is where JSON aggregations come in. This is where you can ask Postgres to send the response back as a JSON that doesn’t need any manual transformations before returning it to the client.

Hasura doesn’t do any of the processing apart from generating the prepared statement with json aggregation. This typically results in a performance improvement of 5x to 8x as opposed to doing manual data transformations to JSON.

Hasura's Query Caching

If you look at the Data Wrapper layer in the Architecture above, you can see that every GraphQL query is processed in three steps:

  • parsing
  • validation
  • planning

These steps are inexpensive, but they do take time.

Hasura maintains an internal cache to improve this process. When a GraphQL query plan is created, the query string and variable values are stored in an internal cache, paired with the prepared SQL. The next time the same query is received, parsing and validation of the GraphQL query can be skipped, and the prepared statement can be executed directly.

Currently, only queries and subscriptions are cached—not mutations—but most queries can be cached. Simple queries that do not contain variables are trivially cacheable, but using variables allows Hasura to create a parameterized query plan that can be reused even if variable values change. This is possible as long as query variables only contain scalar values, but more complex variables may defeat the cache, as different variable values may require different SQL to be generated (since they may change which filters are used in a boolean expression, for example).

By default, cached query plans are retained until the next schema change. Optionally, the --query-plan-cache-size option can be used to set a maximum number of plans that can be simultaneously cached, which may reduce memory usage if an application makes dynamically-generated queries, oversaturating the plan cache. If this option is set, plans are evicted from the cache as needed using a LRU eviction policy.

Query caching eliminates the parsing/validation for GraphQL queries while prepared statements eliminate the same for PostgreSQL queries. This allows Hasura to be very performant, since queries that hit the cache essentially only pay for the execution cost of the resulting SQL query, nothing more.

Conclusion

Hasura's architecture ensures that 2 "caches" are automatically hit so that performance is high :)

  1. GraphQL query plans get cached at Hasura: This means that the code for SQL generation, adding authorization rules etc doesn't need to run repeatedly.
  2. SQL query plans get cached by Postgres with prepared statements: Given a SQL query, Postgres needs to parse, validate and plan it's own execution for actually fetching data. A prepared statement executes significantly faster, because the entire execution plan is already "prepared" and cached!

Fully functional WhatsApp Clone using Angular, GraphQL, Apollo, TypeScript and PostgreSQL

Fully functional WhatsApp Clone using Angular, GraphQL, Apollo, TypeScript and PostgreSQL

An open-source full-stack example app made with Angular 7.2, TypeScript, GraphQL Subscriptions, GraphQL Code Generator, GraphQL Modules, PostgreSQL and TypeORM.

An open-source full-stack example app made with Angular 7.2, TypeScript, GraphQL Subscriptions, GraphQL Code Generator, GraphQL Modules, PostgreSQL and TypeORM.

You might have seen it around already — an open-source WhatsApp Clone tutorial; a project which was originally started in 2015 by Urigo based on Angular-Meteor and Ionic, and have been throughout different incarnations ever since.

You may have also noticed that we recently published a new React version of the Whatsapp Clone tutorial.

This time around, I’m happy to announce that a new version of the WhatsApp Clone is here, and it’s based on Angular 7.2, Angular CLI 7.3.2, Material-UI, TypeScript, Apollo, GraphQL-Subscriptions, GraphQL Code Generator, GraphQL Modules, PostgreSQL and TypeORM, full with step by step guides to teach you every step of the way.

Click me to go to the tutorial page

We’re back in business!

What is it good for?

This app was built with all the latest and hottest technologies out there. The purpose is simple — it should be a guideline for building a proper app, thus we thought very carefully about the design patterns and architecture used in it, plus, we made sure to cover all communication methods with a GraphQL-back-end in different variations (query, mutation, subscription). This way whenever you’re looking to start a new app, maintain an existing one or upgrade your dev-stack, the WhatsApp-clone can be a great source to start with! It’s full stack and has a complete flow.

Why did we choose this dev-stack?

Angular, GraphQL, Apollo, PostgreSQL and TypeScript for obvious reasons — they are backed by a strong ecosystem that grows rapidly. These technologies can be used in endless variations, and there’s no one way which is the most right of doing so, but we chose a way that makes the most sense for us and that we truly believe in when it comes to building apps. We’ve connected it all with TypeORM, GraphQL-Code-Generator, GraphQL-Modules for the following reasons:

  • The GraphQL back-end was implemented using GraphQL-Modules where logic was splitted into feature based modules. GraphQL-Modules is a library which provides you with the ability to manage and maintain your GraphQL schema in a scalable and reusable way. Not once nor twice I have seen people who struggle with that and get tangled upon their own creation, and with GraphQL-Modules where you have a very defined structure, this problem can be easily solved. You can read more in this series of 7 blog posts about it.
  • Every GraphQL/TypeScript definition was automatically generated with GraphQL-Code-Generator using a single command call. There’s no need to maintain the same thing twice if it already exists in one way or another. This way you don’t have to write TypeScript type definitions for your all your server responses, you get ready-to-use — fully typed Angular services, GraphQL resolvers and GraphQL types.
  • The new version of Angular 7.2 was used with the Angular Material UI and Angular CLI 7.3.2 (and we’ll keep updating the tutorial with the latest versions)
  • We used TypeORM to correctly split the logic of the entities in the database and define the relationships between them. ORMs are controversial these days, but they can help a lot in some cases and we thought a good example could be valuable to the community.

What to expect?

  • The GraphQL back-end was implemented using GraphQL-Modules where logic was splitted into feature based modules. GraphQL-Modules is a library which provides you with the ability to manage and maintain your GraphQL schema in a scalable and reusable way. Not once nor twice I have seen people who struggle with that and get tangled upon their own creation, and with GraphQL-Modules where you have a very defined structure, this problem can be easily solved. You can read more in this series of 7 blog posts about it.
  • Every GraphQL/TypeScript definition was automatically generated with GraphQL-Code-Generator using a single command call. There’s no need to maintain the same thing twice if it already exists in one way or another. This way you don’t have to write TypeScript type definitions for your all your server responses, you get ready-to-use — fully typed Angular services, GraphQL resolvers and GraphQL types.
  • The new version of Angular 7.2 was used with the Angular Material UI and Angular CLI 7.3.2 (and we’ll keep updating the tutorial with the latest versions)
  • We used TypeORM to correctly split the logic of the entities in the database and define the relationships between them. ORMs are controversial these days, but they can help a lot in some cases and we thought a good example could be valuable to the community.

The tutorial goes through every aspect of building the app, starting from the very basics. We will start building a very simple server with a fake db, then we will introduce Authentication, Subscriptions, a real database backed by PostgreSQL and TypeORM plus advanced tooling like GraphQL Code Generator and GraphQL Modules.

This can be extremely useful for those who have little to no background in one of the technologies in our dev-stack.

What’s next?

Right now we implemented a simple REST-based Passport authentication, but we already have PRs[1][2] for Accounts-JS based authentication which will use the GraphQL endpoint instead of traditional REST ones. An additional chapter about Pagination is also expected, as well as a “Performance” chapter tackling the N+1 problem with GraphQL. Our backend has been designed to handle way more features than the ones currently implemented, so be ready because features like the Whatsapp blue ticks are going to land on our clone very soon.

Keep up to date

This tutorial was written using Tortilla — the Tutorial framework.

This means that we will keep upgrading the tutorial with the latest versions of Angular and the other libraries, and instead of doing the same tutorial all over again, you will get a git-diff of how to upgrade the existing tutorial! We care about your time as a developer.

Influence

We want to hear your opinions!

Should we choose another library and technology over another? Could we write the code better/cleaner? Should we add a specific feature to the app? We want to hear it all!

Please tell us now so we could integrate your feedback on the tutorial itself!

We keep evolving the stack and as the tutorial is based on git commits, we can create the same clone with different tech-stacks and compare them on this real app using code diffs.

Soon we will also release yet another version of the Whatsapp Clone, using Ionic, Stencil and Web Components.

If you are good at creating screencast and videos, we would love your help in creating videos for some of the chapters.

Everything is completely free and open source, and we want your help and (not financial) contribution!

Best place would be to open an issue or create a PR on the repositories:

  • The GraphQL back-end was implemented using GraphQL-Modules where logic was splitted into feature based modules. GraphQL-Modules is a library which provides you with the ability to manage and maintain your GraphQL schema in a scalable and reusable way. Not once nor twice I have seen people who struggle with that and get tangled upon their own creation, and with GraphQL-Modules where you have a very defined structure, this problem can be easily solved. You can read more in this series of 7 blog posts about it.
  • Every GraphQL/TypeScript definition was automatically generated with GraphQL-Code-Generator using a single command call. There’s no need to maintain the same thing twice if it already exists in one way or another. This way you don’t have to write TypeScript type definitions for your all your server responses, you get ready-to-use — fully typed Angular services, GraphQL resolvers and GraphQL types.
  • The new version of Angular 7.2 was used with the Angular Material UI and Angular CLI 7.3.2 (and we’ll keep updating the tutorial with the latest versions)
  • We used TypeORM to correctly split the logic of the entities in the database and define the relationships between them. ORMs are controversial these days, but they can help a lot in some cases and we thought a good example could be valuable to the community.

Again, all types of feedback is welcome, write freely!

Thanks for reading ❤

Node, Express, PostgreSQL, Vue 2 and GraphQL CRUD Web App

Node, Express, PostgreSQL, Vue 2 and GraphQL CRUD Web App

A comprehensive step by step tutorial on building CRUD Web App using Node, Express, PostgreSQL, Vue 2 and Graphql CRUD Web App

A comprehensive step by step tutorial on building CRUD Web App using Node, Express, PostgreSQL, Vue 2 and Graphql CRUD Web App

For the client side (Vue 2) we will use Vue-Apollo module. For the backend side, we will use Node, Express, Sequelize, and PostgreSQL with Express-Graphql module and their dependencies. The scenario for this tutorial is simple as usual, just the CRUD operation which data accessible through GraphQL.

Table of Contents:
  • Create Express.js Application and Install Required Modules
  • Add and Configure Sequelize.js Module and Dependencies
  • Create or Generate Models and Migrations
  • Install GraphQL Modules and Dependencies
  • Create GraphQL Schemas for the Book
  • Add Mutation for CRUD Operation to the Schema
  • Test GraphQL using GraphiQL
  • Create Vue 2 Application
  • Install Required Modules, Dependencies, and Router
  • Create a Component to Display List of Books
  • Create a Component to Show and Delete Books
  • Create a Component to Add a New Book
  • Create a Component to Edit a Book
  • Run and Test GraphQL CRUD from the Vue 2 Application

The following tools, frameworks, and modules are required for this tutorial:

  • Node.js (choose recommended version)
  • Vue 2
  • Express.js
  • GraphQL
  • Express-GraphQL
  • Vue-Apollo
  • Bootstrap-Vue
  • Terminal (Mac/Linux) or Node Command Line (Windows)
  • IDE or Text Editor (We are using Visual Studio Code)

We assume that you have already Installed Node.js. Make sure Node.js command line is working (on Windows) or runnable in Linux/OS X terminal.

node -v
v10.15.1
npm -v
6.8.0
yarn -v
1.10.1

That the versions that we are uses. Let's continue with the main steps.

1. Create Express.js Application and Install Required Modules

Open your terminal or node command line the go to your projects folder. First, install express generator using this command.

sudo npm install express-generator -g

Next, create an Express.js app using this command.

express vue-graphql

This will create Express.js project with files and directories.

create : vue-graphql/
create : vue-graphql/public/
create : vue-graphql/public/javascripts/
create : vue-graphql/public/images/
create : vue-graphql/public/stylesheets/
create : vue-graphql/public/stylesheets/style.css
create : vue-graphql/routes/
create : vue-graphql/routes/index.js
create : vue-graphql/routes/users.js
create : vue-graphql/views/
create : vue-graphql/views/error.jade
create : vue-graphql/views/index.jade
create : vue-graphql/views/layout.jade
create : vue-graphql/app.js
create : vue-graphql/package.json
create : vue-graphql/bin/
create : vue-graphql/bin/www

Next, go to the newly created project folder then install node modules.

cd vue-graphql && npm install

There's no view yet using the latest Express generator. We don't need it because we will create a GraphQL server.

2. Add and Configure Sequelize.js Module and Dependencies

Before installing the modules for this project, first, install Sequelize-CLI by type this command.

sudo npm install -g sequelize-cli

To install Sequelize.js module, type this command.

npm install --save sequelize

Then install the module for PostgreSQL.

npm install --save pg pg-hstore

Next, create a new file at the root of the project folder.

touch .sequelizerc

Open and edit that file then add these lines of codes.

const path = require('path');

module.exports = {
  "config": path.resolve('./config', 'config.json'),
  "models-path": path.resolve('./models'),
  "seeders-path": path.resolve('./seeders'),
  "migrations-path": path.resolve('./migrations')
};

That files will tell Sequelize initialization to generate config, models, seeders and migrations files to specific directories. Next, type this command to initialize the Sequelize.

sequelize init

That command will create config/config.json, models/index.js, migrations and seeders directories and files. Next, open and edit config/config.json then make it like this.

{
  "development": {
    "username": "djamware",
    "password": "[email protected]@r3",
    "database": "node_sequelize",
    "host": "127.0.0.1",
    "dialect": "postgres"
  },
  "test": {
    "username": "root",
    "password": "[email protected]@r3",
    "database": "node_sequelize",
    "host": "127.0.0.1",
    "dialect": "postgres"
  },
  "production": {
    "username": "root",
    "password": "[email protected]@r3",
    "database": "node_sequelize",
    "host": "127.0.0.1",
    "dialect": "postgres"
  }
}

We use the same configuration for all the environment because we are using the same machine, server, and database for this tutorial.

Before run and test connection, make sure you have created a database as described in the above configuration. You can use the psql command to create a user and database.

psql postgres --u postgres

Next, type this command for creating a new user with password then give access for creating the database.

postgres-# CREATE ROLE djamware WITH LOGIN PASSWORD '[email protected]@r3';
postgres-# ALTER ROLE djamware CREATEDB;

Quit psql then log in again using the new user that previously created.

postgres-# \q
psql postgres -U djamware

Enter the password, then you will enter this psql console.

psql (9.5.13)
Type "help" for help.

postgres=>

Type this command to creating a new database.

postgres=> CREATE DATABASE book_store;

Then give that new user privileges to the new database then quit the psql.

postgres=> GRANT ALL PRIVILEGES ON DATABASE book_store TO djamware;
postgres=> \q

3. Create or Generate Models and Migrations

We will use Sequelize-CLI to generate a new model. Type this command to create a model for 'Book'.

sequelize model:generate --name Book --attributes isbn:string,title:string,author:string,description:string,publishedYear:integer,publisher:string

That commands will generate models and migration files. The content of the model file looks like this.

'use strict';
module.exports = (sequelize, DataTypes) => {
  const Book = sequelize.define('Book', {
    isbn: DataTypes.STRING,
    title: DataTypes.STRING,
    author: DataTypes.STRING,
    description: DataTypes.STRING,
    publishedYear: DataTypes.INTEGER,
    publisher: DataTypes.STRING
  }, {});
  Book.associate = function(models) {
    // associations can be defined here
  };
  return Book;
};

And the migration file looks like this.

'use strict';
module.exports = {
  up: (queryInterface, Sequelize) => {
    return queryInterface.createTable('Books', {
      id: {
        allowNull: false,
        autoIncrement: true,
        primaryKey: true,
        type: Sequelize.INTEGER
      },
      isbn: {
        type: Sequelize.STRING
      },
      title: {
        type: Sequelize.STRING
      },
      author: {
        type: Sequelize.STRING
      },
      description: {
        type: Sequelize.STRING
      },
      publishedYear: {
        type: Sequelize.INTEGER
      },
      publisher: {
        type: Sequelize.STRING
      },
      createdAt: {
        allowNull: false,
        type: Sequelize.DATE
      },
      updatedAt: {
        allowNull: false,
        type: Sequelize.DATE
      }
    });
  },
  down: (queryInterface, Sequelize) => {
    return queryInterface.dropTable('Books');
  }
};

Finally, for migrations, there's nothing to change and they all ready to generate the table to the PostgreSQL Database. Type this command to generate the table to the database.

sequelize db:migrate

4. Install GraphQL Modules and Dependencies

Now, the GraphQL time. Type this command to install GraphQL modules and it's dependencies.

npm install express express-graphql graphql graphql-date cors --save

Next, open and edit app.js then declare all of those modules and dependencies.

var graphqlHTTP = require('express-graphql');
var schema = require('./graphql/bookSchemas');
var cors = require("cors");

The schema is not created yet, we will create it in the next steps. Next, add these lines of codes for configuring GraphQL that can use over HTTP.

app.use('*', cors());
app.use('/graphql', cors(), graphqlHTTP({
  schema: schema,
  rootValue: global,
  graphiql: true,
}));

That's configuration are enabled CORS and the GraphiQL. GraphiQL is the user interface for testing GraphQL query.

5. Create GraphQL Schemas for the Book

Create a folder at the server folder for hold GraphQL Schema files then create a Javascript file for the schema.

mkdir graphql
touch graphql/bookSchemas.js

Next, open and edit server/graphql/bookSchemas.js then declares all required modules and models.

var GraphQLSchema = require('graphql').GraphQLSchema;
var GraphQLObjectType = require('graphql').GraphQLObjectType;
var GraphQLList = require('graphql').GraphQLList;
var GraphQLObjectType = require('graphql').GraphQLObjectType;
var GraphQLNonNull = require('graphql').GraphQLNonNull;
var GraphQLID = require('graphql').GraphQLID;
var GraphQLString = require('graphql').GraphQLString;
var GraphQLInt = require('graphql').GraphQLInt;
var GraphQLDate = require('graphql-date');
var BookModel = require('../models').Book;

Create a GraphQL Object Type for Book models.

var bookType = new GraphQLObjectType({
  name: "book",
  fields: function() {
    return {
      id: {
        type: GraphQLInt
      },
      isbn: {
        type: GraphQLString
      },
      title: {
        type: GraphQLString
      },
      author: {
        type: GraphQLString
      },
      description: {
        type: GraphQLString
      },
      publishedYear: {
        type: GraphQLInt
      },
      publisher: {
        type: GraphQLString
      },
      createdAt: {
        type: GraphQLDate
      },
      updatedAt: {
        type: GraphQLDate
      }
    };
  }
});

Next, create a GraphQL query type that calls a list of book and single book by ID.

var queryType = new GraphQLObjectType({
  name: 'Query',
  fields: function () {
    return {
      books: {
        type: new GraphQLList(bookType),
        resolve: function () {
          const books = BookModel.findAll({
            order: [
              ['createdAt', 'DESC']
            ],
          })
          if (!books) {
            throw new Error('Error')
          }
          return books
        }
      },
      book: {
        type: bookType,
        args: {
          id: {
            name: 'id',
            type: GraphQLString
          }
        },
        resolve: function (root, params) {
          const bookDetails = BookModel.findByPk(params.id).exec()
          if (!bookDetails) {
            throw new Error('Error')
          }
          return bookDetails
        }
      }
    }
  }
});

Finally, exports this file as GraphQL schema by adding this line at the end of the file.

module.exports = new GraphQLSchema({query: queryType});

6. Add Mutation for CRUD Operation to the Schema

For completing CRUD (Create, Read, Update, Delete) operation of the GraphQL, we need to add a mutation that contains create, update and delete operations. Open and edit graphql/bookSchemas.js then add this mutation as GraphQL Object Type.

var mutation = new GraphQLObjectType({
  name: 'Mutation',
  fields: function () {
    return {
      addBook: {
        type: bookType,
        args: {
          isbn: {
            type: new GraphQLNonNull(GraphQLString)
          },
          title: {
            type: new GraphQLNonNull(GraphQLString)
          },
          author: {
            type: new GraphQLNonNull(GraphQLString)
          },
          description: {
            type: new GraphQLNonNull(GraphQLString)
          },
          publishedYear: {
            type: new GraphQLNonNull(GraphQLInt)
          },
          publisher: {
            type: new GraphQLNonNull(GraphQLString)
          }
        },
        resolve: function (root, params) {
          const bookModel = new BookModel(params);
          const newBook = bookModel.save();
          if (!newBook) {
            throw new Error('Error');
          }
          return newBook
        }
      },
      updateBook: {
        type: bookType,
        args: {
          id: {
            name: 'id',
            type: new GraphQLNonNull(GraphQLInt)
          },
          isbn: {
            type: new GraphQLNonNull(GraphQLString)
          },
          title: {
            type: new GraphQLNonNull(GraphQLString)
          },
          author: {
            type: new GraphQLNonNull(GraphQLString)
          },
          description: {
            type: new GraphQLNonNull(GraphQLString)
          },
          publishedYear: {
            type: new GraphQLNonNull(GraphQLInt)
          },
          publisher: {
            type: new GraphQLNonNull(GraphQLString)
          }
        },
        resolve(root, params) {
          return BookModel
          .findByPk(params.id)
          .then(book => {
            if (!book) {
              throw new Error('Not found');
            }
            return book
              .update({
                isbn: params.isbn || book.isbn,
                title: params.title || book.title,
                author: params.author || book.author,
                description: params.description || book.description,
                publishedYear: params.publishedYear || book.publishedYear,
                publisher: params.publisher || book.publisher,
              })
              .then(() => { return book; })
              .catch((error) => { throw new Error(error); });
          })
          .catch((error) => { throw new Error(error); });
        }
      },
      removeBook: {
        type: bookType,
        args: {
          id: {
            type: new GraphQLNonNull(GraphQLInt)
          }
        },
        resolve(root, params) {
          return BookModel
          .findByPk(params.id)
          .then(book => {
            if (!book) {
              throw new Error('Not found');
            }
            return book
              .destroy()
              .then(() => { return book; })
              .catch((error) => { throw new Error(error); });
          })
          .catch((error) => { throw new Error(error); });
        }
      }
    }
  }
});

Finally, add this mutation to the GraphQL Schema exports like below.

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

7. Test GraphQL using GraphiQL

To test the queries and mutations of CRUD operations, re-run again the Express.js app then open the browser. Go to this address [http://localhost:3000/graphql](http://localhost:3000/graphql "http://localhost:3000/graphql") to open the GraphiQL User Interface.

To get the list of books, replace all of the text on the left pane with this GraphQL query then click the Play button.

To get a single book by ID, use this GraphQL query.

{
  book(id: 1) {
    id
    isbn
    title
    author
    description
    publishedYear
    publisher
    updatedAt
  }
}

To add a book, use this GraphQL mutation.

mutation {
  addBook(
    isbn: "12345678",
    title: "Whatever this Book Title",
    author: "Mr. Bean",
    description: "The short explanation of this Book",
    publisher: "Djamware Press",
    publishedYear: 2019
  ) {
    updatedAt
  }
}

You will the response at the right pane like this.

{
  "data": {
    "addBook": {
      "updatedAt": "2019-02-26T13:55:39.160Z"
    }
  }
}

To update a book, use this GraphQL mutation.

mutation {
  updateBook(
    id: 1,
    isbn: "12345678221",
    title: "The Learning Curve of GraphQL",
    author: "Didin J.",
    description: "The short explanation of this Book",
    publisher: "Djamware Press",
    publishedYear: 2019
  ) {
    id,
    updatedAt
  }
}

You will see the response in the right pane like this.

{
  "data": {
    "updateBook": {
      "id": 1,
      "updated_date": "2019-02-26T13:58:35.811Z"
    }
  }
}

To delete a book by ID, use this GraphQL mutation.

mutation {
  removeBook(id: 1) {
    id
  }
}

You will see the response in the right pane like this.

{
  "data": {
    "removeBook": {
      "id": 1
    }
  }
}

8. Create Vue 2 Application

To install Vue-CLI type this command from the Terminal or Node command line.

sudo npm install -g @vue/cli

or

yarn global add @vue/cli

Next, check the version to make sure that you have the 3.x version of Vue-CLI.

vue --version
3.7.0

Next, create a new Vue.js project by type this command.

vue create client

For now, use the default for every question that shows up in the Terminal. Next, go to the newly created folder.

cd ./client

To make sure that created Vue.js project working, type this command to run the Vue.js application.

npm run serve

or

yarn serve

You will see this page when open [http://localhost:8080/](http://localhost:8080/ "http://localhost:8080/") in the browser.

9. Install/Configure the Required Modules, Dependencies, and Router

Now, we have to install and configure all of the required modules and dependencies. Type this command to install the modules.

npm install apollo-boost vue-apollo graphql-tag graphql vue-router --save

Next, open and edit src/main.js then add these imports.

import ApolloClient from "apollo-boost";
import VueApollo from "vue-apollo";

Add these constant variables then register VueApollo in Vue 2 app.

const apolloClient = new ApolloClient({
  uri: 'http://localhost:3000/graphql'
});

const apolloProvider = new VueApollo({
  defaultClient: apolloClient
});

Vue.use(VueApollo);

new Vue({
  apolloProvider,
  render: h => h(App)
}).$mount('#app')

To register or create routes for the whole application navigation, create a router folder and index.js file.

mkdir src/router
touch src/router/index.js

Open and edit src/router/index.js then add these imports.

import VueRouter from 'vue-router'
import BookList from '@/components/BookList'
import ShowBook from '@/components/ShowBook'
import AddBook from '@/components/AddBook'
import EditBook from '@/components/EditBook'

Add the router to each component or page.

export default new VueRouter({
  routes: [
    {
      path: '/',
      name: 'BookList',
      component: BookList
    },
    {
      path: '/show-book/:id',
      name: 'ShowBook',
      component: ShowBook
    },
    {
      path: '/add-book',
      name: 'AddBook',
      component: AddBook
    },
    {
      path: '/edit-book/:id',
      name: 'EditBook',
      component: EditBook
    }
  ]
})

Add Vue files for above-registered components or pages.

touch src/components/BookList.vue
touch src/components/ShowBook.vue
touch src/components/AddBook.vue
touch src/components/EditBook.vue

Finally, add or register this router file to src/main.js by adding these imports.

import VueRouter from 'vue-router'
import router from './router'

Register the Vue-Router after Vue.config.

Vue.use(VueRouter)

Modify new Vue to be like this.

new Vue({
  apolloProvider,
  router,
  render: h => h(App)
}).$mount('#app')

10. Create a Component to Display List of Books

Before create or show data to the views, we have to add Bootstrap-Vue. Type this command to install the module.

npm i bootstrap-vue

Next, open and edit src/main.js then add these imports.

import BootstrapVue from 'bootstrap-vue'
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'

Add this line after Vue.config.

Vue.use(BootstrapVue);

Now, open and edit src/components/BookList.vue then add this template tags that contain a bootstrap-vue table.

<template>
  <b-row>
    <b-col cols="12">
      <h2>
        Book List
        <b-link href="#/add-Book">(Add Book)</b-link>
      </h2>
      <b-table striped hover :items="books" :fields="fields">
        <template slot="actions" scope="row">
          <b-btn size="sm" @click.stop="details(row.item)">Details</b-btn>
        </template>
      </b-table>
    </b-col>
  </b-row>
</template>

Next, add the script tag for hold all Vue 2 codes.

<script></script>

Inside the script tag, add these imports.

import gql from "graphql-tag";
import router from "../router";

Declare the constant variables for GraphQL query.

const GET_BOOKS = gql`
  {
    books {
      id
      title
      author
    }
  }
`;

Add the main Vue 2 export that contains Vue-Apollo calls that filled Vue 2 data.

export default {
  name: "BookList",
  apollo: {
    books: {
      query: GET_BOOKS,
      pollInterval: 300
    }
  },
  data() {
    return {
      fields: {
        title: { label: "Title", sortable: true, class: "text-left" },
        author: { label: "Author", sortable: true, class: "text-left" },
        actions: { label: "Action", class: "text-center" }
      },
      books: []
    };
  },
  methods: {
    details(book) {
      router.push({ name: "ShowBook", params: { id: book.id } });
    }
  }
};

Finally, add the style tag for styling the template.

<style>
.table {
  width: 96%;
  margin: 0 auto;
}
</style>

11. Create a Component to Show and Delete Books

To show the book details that contains all book detail, edit and delete buttons, open and edit src/components/ShowBook.vue then add these template tags that contain a bootstrap-vue component for display the details.

<template>
  <b-row>
    <b-col cols="12">
      <h2>
        Book List
        <b-link href="#/">(Book List)</b-link>
      </h2>
      <b-jumbotron>
        <template slot="header">{{book.title}}</template>
        <template slot="lead">
          ISBN: {{book.isbn}}
          <br>
          Author: {{book.author}}
          <br>
          Description: {{book.description}}
          <br>
          Published Year: {{book.publishedYear}}
          <br>
          Publisher: {{book.publisher}}
          <br>
          Update At: {{book.updatedAt}}
          <br>
        </template>
        <hr class="my-4">
        <b-btn class="edit-btn" variant="success" @click.stop="editBook(book.id)">Edit</b-btn>
        <b-btn variant="danger" @click.stop="deleteBook(book.id)">Delete</b-btn>
      </b-jumbotron>
    </b-col>
  </b-row>
</template>

Next, add the script tag.

<script></script>

Inside the script tag, add these imports.

import gql from "graphql-tag";
import router from "../router";

Declare the constant variables that handle get a single book and delete book queries.

const GET_BOOK = gql`
  query book($bookId: Int) {
    book(id: $bookId) {
      id
      isbn
      title
      author
      description
      publishedYear
      publisher
      updatedAt
    }
  }
`;

const DELETE_BOOK = gql`
  mutation removeBook($id: Int!) {
    removeBook(id: $id) {
      id
    }
  }
`;

Inside main Vue export, add all required functions, variables, and Vue-Apollo function.

export default {
  name: "ShowBook",
  data() {
    return {
      book: '',
      bookId: parseInt(this.$route.params.id)
    };
  },
  apollo: {
    book: {
      query: GET_BOOK,
      pollInterval: 300,
      variables() {
        return {
          bookId: this.bookId
        };
      }
    }
  },
  methods: {
    editBook(id) {
      router.push({
        name: "EditBook",
        params: { id: id }
      });
    },
    deleteBook(id) {
      this.$apollo
        .mutate({
          mutation: DELETE_BOOK,
          variables: {
            id: id
          }
        })
        .then(data => {
          console.log(data);
        })
        .catch(error => {
          console.error(error);
        });
    }
  }
};

Finally, add the style tags to give the view some styles.

<style>
.jumbotron {
  padding: 2rem;
}
.edit-btn {
  margin-right: 20px;
  width: 70px;
}
</style>

12. Create a Component to Add a New Book

To add a new book, open and edit src/components/AddBook.vue then add this Vue 2 template tag that contains a bootstrap-vue form.

<template>
  <b-row>
    <b-col cols="12">
      <h2>
        Add Book
        <b-link href="#/">(Book List)</b-link>
      </h2>
      <b-jumbotron>
        <b-form @submit="onSubmit">
          <b-form-group
            id="fieldsetHorizontal"
            horizontal
            :label-cols="4"
            breakpoint="md"
            label="Enter ISBN"
          >
            <b-form-input id="isbn" v-model.trim="book.isbn"></b-form-input>
          </b-form-group>
          <b-form-group
            id="fieldsetHorizontal"
            horizontal
            :label-cols="4"
            breakpoint="md"
            label="Enter Title"
          >
            <b-form-input id="title" v-model.trim="book.title"></b-form-input>
          </b-form-group>
          <b-form-group
            id="fieldsetHorizontal"
            horizontal
            :label-cols="4"
            breakpoint="md"
            label="Enter Author"
          >
            <b-form-input id="author" v-model.trim="book.author"></b-form-input>
          </b-form-group>
          <b-form-group
            id="fieldsetHorizontal"
            horizontal
            :label-cols="4"
            breakpoint="md"
            label="Enter Description"
          >
            <b-form-textarea
              id="description"
              v-model="book.description"
              placeholder="Enter something"
              :rows="2"
              :max-rows="6"
            >{{book.description}}</b-form-textarea>
          </b-form-group>
          <b-form-group
            id="fieldsetHorizontal"
            horizontal
            :label-cols="4"
            breakpoint="md"
            label="Enter Publisher"
          >
            <b-form-input id="publisher" v-model.trim="book.publisher"></b-form-input>
          </b-form-group>
          <b-form-group
            id="fieldsetHorizontal"
            horizontal
            :label-cols="4"
            breakpoint="md"
            label="Enter Published Year"
          >
            <b-form-input type="number" id="publishedYear" v-model.trim="book.publishedYear"></b-form-input>
          </b-form-group>
          <b-button type="submit" variant="primary">Save</b-button>
        </b-form>
      </b-jumbotron>
    </b-col>
  </b-row>
</template>

Next, add the script tag.

<script></script>

Inside the script, tag adds Vue 2 codes that contain Vue-Apollo GraphQL mutation to save a new book.

import gql from "graphql-tag";
import router from "../router";

const ADD_BOOK = gql`
  mutation AddBook(
    $isbn: String!
    $title: String!
    $author: String!
    $description: String!
    $publisher: String!
    $publishedYear: Int!
  ) {
    addBook(
      isbn: $isbn
      title: $title
      author: $author
      description: $description
      publisher: $publisher
      publishedYear: $publishedYear
    ) {
      id
    }
  }
`;

export default {
  name: "AddBook",
  data() {
    return {
      book: {}
    };
  },
  methods: {
    onSubmit(evt) {
      evt.preventDefault();

      this.$apollo
        .mutate({
          mutation: ADD_BOOK,
          variables: {
            isbn: this.book.isbn,
            title: this.book.title,
            author: this.book.author,
            description: this.book.description,
            publisher: this.book.publisher,
            publishedYear: parseInt(this.book.publishedYear)
          }
        })
        .then(data => {
          console.log(data);
          router.push({ name: "BookList" });
        })
        .catch(error => {
          console.error(error);
        });
    }
  }
};

Finally, give the view a style by adding the style tag.

<style>
.jumbotron {
  padding: 2rem;
}
</style>

13. Create a Component to Edit a Book

To edit a book after getting single book data, open and edit src/components/EditBook.vue then add this Vue 2 template that contains a bootstrap-vue form.

<template>
  <b-row>
    <b-col cols="12">
      <h2>
        Edit Book
        <router-link :to="{ name: 'ShowBook', params: { id: bookId } }">(Show Book)</router-link>
      </h2>
      <b-jumbotron>
        <b-form @submit="onSubmit">
          <b-form-group
            id="fieldsetHorizontal"
            horizontal
            :label-cols="4"
            breakpoint="md"
            label="Enter ISBN"
          >
            <b-form-input id="isbn" v-model.trim="book.isbn"></b-form-input>
          </b-form-group>
          <b-form-group
            id="fieldsetHorizontal"
            horizontal
            :label-cols="4"
            breakpoint="md"
            label="Enter Title"
          >
            <b-form-input id="title" v-model.trim="book.title"></b-form-input>
          </b-form-group>
          <b-form-group
            id="fieldsetHorizontal"
            horizontal
            :label-cols="4"
            breakpoint="md"
            label="Enter Author"
          >
            <b-form-input id="author" v-model.trim="book.author"></b-form-input>
          </b-form-group>
          <b-form-group
            id="fieldsetHorizontal"
            horizontal
            :label-cols="4"
            breakpoint="md"
            label="Enter Description"
          >
            <b-form-textarea
              id="description"
              v-model="book.description"
              placeholder="Enter something"
              :rows="2"
              :max-rows="6"
            >{{book.description}}</b-form-textarea>
          </b-form-group>
          <b-form-group
            id="fieldsetHorizontal"
            horizontal
            :label-cols="4"
            breakpoint="md"
            label="Enter Publisher"
          >
            <b-form-input id="publisher" v-model.trim="book.publisher"></b-form-input>
          </b-form-group>
          <b-form-group
            id="fieldsetHorizontal"
            horizontal
            :label-cols="4"
            breakpoint="md"
            label="Enter Published Year"
          >
            <b-form-input type="number" id="publishedYear" v-model.trim="book.publishedYear"></b-form-input>
          </b-form-group>
          <b-button type="submit" variant="primary">Update</b-button>
        </b-form>
      </b-jumbotron>
    </b-col>
  </b-row>
</template>

Next, add the script tag that contains all required Vue 2 codes with get data and update function.

<script>
import gql from "graphql-tag";
import router from "../router";

const GET_BOOK = gql`
  query book($bookId: Int) {
    book(id: $bookId) {
      id
      isbn
      title
      author
      description
      publishedYear
      publisher
    }
  }
`;

const UPDATE_BOOK = gql`
  mutation updateBook(
    $id: Int!
    $isbn: String!
    $title: String!
    $author: String!
    $description: String!
    $publisher: String!
    $publishedYear: Int!
  ) {
    updateBook(
      id: $id
      isbn: $isbn
      title: $title
      author: $author
      description: $description
      publisher: $publisher
      publishedYear: $publishedYear
    ) {
      updatedAt
    }
  }
`;

export default {
  name: "EditBook",
  data() {
    return {
      bookId: this.$route.params.id,
      book: {}
    };
  },
  apollo: {
    book: {
      query: GET_BOOK,
      variables() {
        return {
          bookId: this.bookId
        };
      }
    }
  },
  methods: {
    onSubmit(evt) {
      evt.preventDefault();

      this.$apollo
        .mutate({
          mutation: UPDATE_BOOK,
          variables: {
            id: parseInt(this.book.id),
            isbn: this.book.isbn,
            title: this.book.title,
            author: this.book.author,
            description: this.book.description,
            publisher: this.book.publisher,
            publishedYear: parseInt(this.book.publishedYear)
          }
        })
        .then(data => {
          console.log(data);
          router.push({
            name: "ShowBook",
            params: { id: this.$route.params.id }
          });
        })
        .catch(error => {
          console.error(error);
        });
    }
  }
};
</script>

Finally, give the view some style by adding the style tag.

<style>
.jumbotron {
  padding: 2rem;
}
</style>

14. Run and Test GraphQL CRUD from the Vue 2 Application

We assume the PostgreSQL server already running, so you just can run Node/Express.js application and Vue 2 app in the separate terminal tabs.

nodemon
cd client
npm run serve

Next, open the browser then go to this address localhost:8080 and you should see these pages.

That it's, the Node, Express, PostgreSQL, Vue 2 and Graphql CRUD Web App. You can find the full source code on our GitHub.

Build a React Native app with PostgreSQL and GraphQL - Part 1

Build a React Native app with PostgreSQL and GraphQL - Part 1

In this two-part series, we’ll be looking at how we can develop an application with React Native and GraphQL. To do this, we’ll be building a simple note-taking app that allows our user to add notes, view, edit and delete them.

React Native is a great option when it comes to developing mobile applications for both iOS and Android smartphones. With React Native you can write an application that works on both platforms with the only difference coming at the view level, with iOS and Android rendering differently. In this two-part series, we’ll be looking at how we can develop an application with React Native and GraphQL. To do this, we’ll be building a simple note-taking app that allows our user to add notes, view, edit and delete them.

For the first part, we’ll be building a GraphQL server with a PostgreSQL database that will serve as the backend of our application. In the second part (blog post to come later) we will build an application with React Native that will consume data from our server.

Let’s get started with our server setup. The folder structure for our project will be as shown in this repo.

In order to build our application, you will need to be familiar with:

  • NodeJS
  • Express
  • Databases
  • Mobile development
How to set up an Apollo server with Express

Our backend server will run on Node JS and Express. We’ll have a few initial dependencies to install, but first, we’ll need to initiate our app, run:

npm init

Now let us install our first set of dependencies:

npm install apollo-server apollo-server-express express graphql cors --save

One of the dependencies we have installed is apollo-server, Apollo Server is the best way to quickly build a production-ready, self-documenting API for GraphQL clients, using data from any source. While we are using it with Express, it may be used with several popular libraries such as Hapi and Koa.

Now we are all set to start creating our server. Add the following code to src/index.js.

import cors from 'cors';
import express from 'express';
import {
    ApolloServer
} from 'apollo-server-express';

const app = express();

app.use(cors());

const server = new ApolloServer({});

server.applyMiddleware({
app,
path: '/graphql'
});

app.listen({
port: 8000
}, () => {
console.log('Apollo Server on http://localhost:8000/graphql');
});

Let’s go through what we have so far, in the code above, we have initialized an Express application as well as an Apollo server instance. Using Apollo Server’s applyMiddleware() method, you can opt-in any middleware, which in our case is the Express app. Also, you can specify the path for your GraphQL API endpoint.

You may also note we applied cors to our Express application, this is to allow external domains to access our application.

Our next step would be to define our schema and resolvers for the Apollo Server instance. But first, let’s cover schema and resolvers.

Schema

The GraphQL schema provided to the Apollo Server is all of the available data for reading and writing data via GraphQL. The schema contains type definitions from a mandatory top-level Query type that allows reading of data followed by field and nested fields. These are defined using the GraphQL Schema Language and allows us to talk about GraphQL schemas in a language-agnostic way.

Here’s what’s in our schema/notes.js:

import {
gql
} from 'apollo-server-express';

export default gql `
extend type Query {
notes: [Note!]
note(id: ID!): Note!
}

type Note {
id: ID!
text: String!
}
`;

Let’s take a look at our schema definition for some notable terms we need to be aware of. We have already covered the Query type, but let’s look at the rest:

  • notes is defined as [Note!] which represents an array of Note objects. The exclamation mark means it is non-nullable, this means you can always expect an array (with zero or more items) when you query the notes field
  • Each Note object contains an id field defined as ID . The ID scalar type represents a unique identifier, often used to re-fetch an object or as the key for a cache. It is serialized the same way a String is serialized. However, defining it as an ID signifies that it is not intended to be human‐readable
  • The Note object also has a text field defined as String which is one of the built-in scalar types that is a sequence of UTF-8 characters
  • You will also notice the note field is provided with id options in the form of (id: ID!). This allows for the use of GraphQL arguments to refine queries. This allows us to fetch a single note by providing it’s ID in our query. The argument is also non-nullable as it must be provided when fetching a single note.

The schema above shall be placed in src/schema/notes.js. We can then have a index.js file inside the src/schema directory with the following code:

import { gql } from "apollo-server-express";

import noteSchema from "./notes";

const linkSchema = gql`
  type Query {
    _: Boolean
  }

  type Mutation {
    _: Boolean
  }

  type Subscription {
    _: Boolean
  }
`;

export default [linkSchema, noteSchema];

Resolvers

Resolvers are responsible for manipulating and returning data, think of them as the query handlers. Each top-level query in the Query type has a resolver but we’ll make our own per field. The resolvers are grouped in a JavaScript object, often called a resolver map.

Let’s create a notes.js file inside src/resolvers and add the following code to it:

export default {
Query: {
notes: (parent, args, {
models
}) => {
return Object.values(models.notes)
},
note: (parent, {
id
}, {
models
}) => {
return models.notes[id]
}
}
}

The above resolvers allow us to query either individual notes using the note field and provide an id argument or an array of notes using the notes field. We’ll clean up our resolvers once we link our app to the database using sequelize and add Mutations which will allow us to carry out operations other than reading on our data. For now, these resolve dummy data that we’ll have in src/models/index.js which contains the following code:

let notes = {
1: {
id: '1',
text: 'Hello World',
},
2: {
id: '2',
text: 'By World',
},
};

export default {
notes
};

Now that we have the framework set up for how our data will be stored let’s look at how data manipulation will be done using queries and mutations.

Queries and mutations

Queries and mutations in GraphQL allow us to access and manipulate data on a GraphQL server. Queries are in charge of read operations whereas mutations are in charge of create, update and delete operations.

To create our queries and mutations, we must first define them in our schema. Let’s edit src/schema/notes.js as follows:

import {
gql
} from 'apollo-server-express';

export default gql `
extend type Query {
notes: [Note!]
note(id: ID!): Note!
}

extend type Mutation {
createNewNote(text: String!): Note!
deleteNote(id: ID!): Boolean!
updateNote(id: ID!, text: String!): Note!
}

type Note {
id: ID!
text: String!
}
`;

Queries have already been defined earlier so our new addition is the Mutationtype. From the types defined, you can get an idea of what the returned data will look like. For instance, createNewNote takes a text argument that is a string and generates and returns a Note object, updateNote is similar but it also takes the id so that it can find the note to be updated. Meanwhile, deleteNote takes an idboolean that is used to identify the target note and returns a boolean indicating whether the note has been deleted successfully or not.

This is how you can test your queries. Type the following code in your graphql playground:

Get all notes

Fetches and returns all notes saved.

query {
notes {
id
text
}
}

Next hit the Execute Query button and you should be able to see the following result:

Result

{
"data": {
"notes": [
{
"id": "1",
"text": "Hello World"
},
{
"id": "3",
"text": "By World"
}
]
}
}

Get note by id

This returns a piece of the data matching the given argument id

query{
note(id: "1") {
id
text
}
}

Result

{
"data": {
"note": {
"id": "1",
"text": "Hello World"
}
}

Next, we’ll need to write a Mutation resolver. Let’s editsrc/resolvers/notes.js

like this:

import uuidv4 from 'uuid/v4';

export default {
Query: {
notes: (parent, args, {
models
}) => {
return Object.values(models.notes)
},
note: (parent, {
id
}, {
models
}) => {
return models.notes[id]
}
},
Mutation: {
createNewNote: (parent, {
text
}, {
models
}) => {
const id = uuidv4();
const newNote = {
id,
text
}
models.notes[id] = newNote;
return newNote;
},

    deleteNote: (parent, {
        id
    }, {
        models
    }) =&gt; {
        const {
            [id]: note, ...otherNotes
        } = models.notes
        if (!note) {
            return false
        }
        models.notes = otherNotes
        return true
    },

}

For the createNewNote Mutation we are generating the unique id using the uuid/v4 library so we’ll need to install it by running:

npm install uuid/v4 --save

The resolver takes a text argument, generates a unique id and uses them to create a Note object which is then appended to the list of notes. It then returns the new note that has been created.

The deleteNote Mutation takes an id argument, finds a note and creates a new notes object without the note to be deleted and replaces the old notes object with the new one. It returns false if the note is not found and true when the note is found and deleted.

The updateNote Mutation takes the id and text as arguments and updates the Note in the notes object accordingly, returning the updated note at the end.

We can now access these endpoints through the GraphQL playground when we run our application by running npm start and going to localhost:3000/graphql on the browser. You can then try out these actions:

Create a new note

Takes a text argument and creates and returns a note.

mutation {
createNewNote( text: "Hello GraphQl") {
id
text
}
}

Result

{
"data": {
"createNewNote": {
"id": "3",
"text": "Hello GraphQl"
}
}
}

Update note

Takes text and id arguments and updates and returns a note.

mutation {
updateNote(id:3, text:"Graphql is awesome") {
id
text
}
}

Result

{
"data": {
"updateNote": {
"id": "4",
"text": "Graphql is awesome"
}
}
}

Delete note

Takes an id argument and deletes the matching note.

mutation {
deleteNote(id:5)
}
Adding PostgreSQL with Sequelize

We have most of our app ready to go so the next thing we need to look at is how we can save our data to a database. Our database will be PostgreSQL so go ahead and install it from their site then start it.

After that, create a database called mynotes. To do this you can run the command psql in your terminal to open up the PosgreSQL CLI. Once open run:

CREATE DATABASE mynotes;
CREATE USER postgres;
GRANT ALL PRIVILEGES ON DATABASE members TO postgres;

Next you’ll want to add PostgreSQL for Node.js and Sequelize (ORM) to your project. Run:

npm install pg sequelize --save

The next step is to update our models to prepare to link to the database as shown below. The model defines the shape of each piece of data as it will be stored in the database:

const note = (sequelize, DataTypes) => {
const Note = sequelize.define("note", {
text: DataTypes.STRING
});
return Note;
};

export default note;

Next, connect to your database from within your application in the src/models/index.js file:

import Sequelize from "sequelize";

const sequelize = new Sequelize(
process.env.DATABASE,
process.env.DATABASE_USER,
process.env.DATABASE_PASSWORD,
{
dialect: "postgres"
}
);

const models = {
Note: sequelize.import("./notes")
};

Object.keys(models).forEach((key) => {
if ("associate" in models[key]) {
models[key].associate(models);
}
});

export { sequelize };
export default models;

You’ll need to define the database name, a database super-user, and the user’s password. You may also want to define a database dialect because Sequelize supports other databases as well.

In the same file, you can physically associate all your models with each other if you have multiple models (hence the foreEach loop above) to expose them to your application as data access layer (models) for the database. In our case, we only have one model but this is still good to know.

Now let’s create a .env file at the root directory of the project to hold some critical data as environment variables. The database credentials (database name, database super user name, database super-user password) can be stored as environment variables along with the port on which we wish to run our server.

In the .env file add the following variables or change them to your liking:

PORT=3000
DATABASE=mynotes
DATABASE_USER=postgres
DATABASE_PASSWORD=postgres

Next, let’s update src/index.js to get our databases synchronized when our application is started. Make the following changes:

...
import 'dotenv/config';
import models, {
sequelize
} from './models';
...

sequelize.sync().then(async () => {
app.listen({
port: 8000 // you could change this to process.env.PORT if you wish
}, () => {
console.log('Apollo Server on http://localhost:8000/graphql');
});
});

We’ve now linked our application to the database so what we need to do next is replace the logic in our resolvers.

How to connect resolvers to the database

We had dummy data before but with Sequelize we can now allow us to sync our data with our database, allowing for data persistence. To make this happen, we need to update our resolver in src/resolvers/notes.js to integrate the Sequelize API.

export default {
Query: {
notes: async (parent, args, { models }) => {
return await models.Note.findAll();
},

note: async (parent, { id }, { models }) =&gt; {
  return await models.Note.findByPk(id);
}

},
Mutation: {
createNewNote: async (parent, { text }, { models }) => {
return await models.Note.create({
text
});
},

deleteNote: async (parent, { id }, { models }) =&gt; {
  return await models.Note.destroy({
    where: {
      id
    }
  });
},
updateNote: async (parent, { id, text }, { models }) =&gt; {
  await models.Note.update(
    {
      text
    },
    {
      where: {
        id: id
      }
    }
  );
  const updatedNote = await models.Note.findByPk(id, {
    include
  });
  return updatedNote;
}

}
};

You will notice we no longer need uuid/v4 as Sequelize automatically handles the assignment of ids in our database. The findAll() and findByPk() are commonly used Sequelize methods for database operations, findAll() returns all entries in a certain table whereas findPk() identifies and returns entries that fit a certain criterion such as a matching id in our case.

Other Sequelize methods we make use of include update() and destroy() to update and delete records respectively.

Along with these, we use async/await to implement promise-based asynchronous Javascript requests to our database.

Conclusion

Great! We are all ready to go, the next step is to integrate our GraphQL backend with our front-end app, which we’ll build for mobile in part two of this tutorial.

Thanks For Visiting, Keep Visiting. If you liked this post, share it with all of your programming buddies!


Originally published on blog.logrocket.com