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.

Develop this one fundamental skill if you want to become a successful developer

Throughout my career, a multitude of people have asked me&nbsp;<em>what does it take to become a successful developer?</em>

Throughout my career, a multitude of people have asked me what does it take to become a successful developer?

It’s a common question newbies and those looking to switch careers often ask — mostly because they see the potential paycheck. There is also a Hollywood level of coolness attached to working with computers nowadays. Being a programmer or developer is akin to being a doctor or lawyer. There is job security.

But a lot of people who try to enter the profession don’t make it. So what is it that separates those who make it and those who don’t? 

Read full article here

Node, Express, React.js, Graphql and MongoDB CRUD Web Application

Node, Express, React.js, Graphql and MongoDB CRUD Web Application

In this tutorial, we just change the client side by using React.js and the backend still the same. Don’t worry, we will show the full tutorial from the server and the client side. We are using React Apollo library for fetch GraphQL data.

A comprehensive step by step tutorial on building CRUD (create, read, update, delete) web application using React.js and GraphQL using React-Apollo. Previously, we have to build CRUD web application using Node, Express, Angular 7 and GraphQL.

Table of Contents:
  • Create Express.js App
  • Install and Configure Mongoose.js Modules for Accessing MongoDB
  • Create Mongoose.js Model for the Book Document
  • Install GraphQL Modules and Dependencies
  • Create GraphQL Schemas for the Book
  • Add Mutation for CRUD Operation to the Schema
  • Test GraphQL using GraphiQL
  • Create React.js Application
  • Install and Configure Required Modules and Dependencies
  • Create React.js Router DOM]
  • 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 React.js Application

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

  • Node.js (choose recommended version)
  • React.js
  • Express.js
  • GraphQL
  • Express-GraphQL
  • React Apollo
  • 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.

1. Create Express.js App

If Express.js Generator hasn’t installed, type this command from the terminal or Node.js command prompt.

sudo npm install express-generator -g

The sudo keyword is using in OSX or Linux Terminal otherwise you can use that command without sudo. Before we create an Express.js app, we have to create a root project folder inside your projects folder. From the terminal or Node.js command prompt, type this command at your projects folder.

mkdir node-react-graphql

Go to the newly created directory.

cd ./node-react-graphql

From there, type this command to generate Express.js application.

express server

Go to the newly created Express.js app folder.

cd ./server

Type this command to install all required NPM modules that describe in package.json dependencies.

npm install

To check the Express.js app running smoothly, type this command.

nodemon

or

npm start

If you see this information in the terminal or command prompt that means your Express.js app is ready to use.

[nodemon] 1.18.6
[nodemon] to restart at any time, enter `rs`
[nodemon] watching: *.*
[nodemon] starting `node ./bin/www`

2. Install and Configure Mongoose.js Modules for Accessing MongoDB

To install Mongoose.js and it’s required dependencies, type this command.

npm install mongoose bluebird --save

Next, open and edit app.js then declare the Mongoose module.

var mongoose = require('mongoose');

Create a connection to the MongoDB server using this lines of codes.

mongoose.connect('mongodb://localhost/node-graphql', { promiseLibrary: require('bluebird'), useNewUrlParser: true })
  .then(() =>  console.log('connection successful'))
  .catch((err) => console.error(err));

Now, if you re-run again Express.js server after running MongoDB server or daemon, you will see this information in the console.

[nodemon] 1.18.6
[nodemon] to restart at any time, enter `rs`
[nodemon] watching: *.*
[nodemon] starting `node ./bin/www`
connection successful

That’s mean, the connection to the MongoDB is successful.

3. Create Mongoose.js Model for the Book Document

Before creating a Mongoose.js model that represents Book Document, we have to create a folder at the server folder for hold Models. After that, we can create a Mongoose.js model file.

mkdir models
touch models/Book.js

Open and edit server/models/Book.js then add these lines of codes.

var mongoose = require('mongoose');

var BookSchema = new mongoose.Schema({
  id: String,
  isbn: String,
  title: String,
  author: String,
  description: String,
  published_year: { type: Number, min: 1945, max: 2019 },
  publisher: String,
  updated_date: { type: Date, default: Date.now },
});

module.exports = mongoose.model('Book', BookSchema);

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 server/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: GraphQLString
      },
      isbn: {
        type: GraphQLString
      },
      title: {
        type: GraphQLString
      },
      author: {
        type: GraphQLString
      },
      description: {
        type: GraphQLString
      },
      published_year: {
        type: GraphQLInt
      },
      publisher: {
        type: GraphQLString
      },
      updated_date: {
        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.find().exec()
          if (!books) {
            throw new Error('Error')
          }
          return books
        }
      },
      book: {
        type: bookType,
        args: {
          id: {
            name: '_id',
            type: GraphQLString
          }
        },
        resolve: function (root, params) {
          const bookDetails = BookModel.findById(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 server/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)
          },
          published_year: {
            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(GraphQLString)
          },
          isbn: {
            type: new GraphQLNonNull(GraphQLString)
          },
          title: {
            type: new GraphQLNonNull(GraphQLString)
          },
          author: {
            type: new GraphQLNonNull(GraphQLString)
          },
          description: {
            type: new GraphQLNonNull(GraphQLString)
          },
          published_year: {
            type: new GraphQLNonNull(GraphQLInt)
          },
          publisher: {
            type: new GraphQLNonNull(GraphQLString)
          }
        },
        resolve(root, params) {
          return BookModel.findByIdAndUpdate(params.id, { isbn: params.isbn, title: params.title, author: params.author, description: params.description, published_year: params.published_year, publisher: params.publisher, updated_date: new Date() }, function (err) {
            if (err) return next(err);
          });
        }
      },
      removeBook: {
        type: bookType,
        args: {
          id: {
            type: new GraphQLNonNull(GraphQLString)
          }
        },
        resolve(root, params) {
          const remBook = BookModel.findByIdAndRemove(params.id).exec();
          if (!remBook) {
            throw new Error('Error')
          }
          return remBook;
        }
      }
    }
  }
});

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 <a href="http://localhost:3000/graphql" target="_blank">http://localhost:3000/graphql</a> 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: "5c738dd4cb720f79497de85c") {
    _id
    isbn
    title
    author
    description
    published_year
    publisher
    updated_date
  }
}

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",
    published_year: 2019
  ) {
    updated_date
  }
}

You will the response at the right pane like this.

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

To update a book, use this GraphQL mutation.

mutation {
  updateBook(
    id: "5c75455b146dbc2504b94012",
    isbn: "12345678221",
    title: "The Learning Curve of GraphQL",
    author: "Didin J.",
    description: "The short explanation of this Book",
    publisher: "Djamware Press",
    published_year: 2019
  ) {
    _id,
    updated_date
  }
}

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

{
  "data": {
    "updateBook": {
      "_id": "5c75455b146dbc2504b94012",
      "updated_date": "2019-02-26T13:58:35.811Z"
    }
  }
}

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

mutation {
  removeBook(id: "5c75455b146dbc2504b94012") {
    _id
  }
}

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

{
  "data": {
    "removeBook": {
      "_id": "5c75455b146dbc2504b94012"
    }
  }
}

8. Install and Create React.js Application

Open the terminal or Node.js command line then go to your React.js projects folder. We will install React.js app creator for creating a React.js app easily. For that, type this command.

sudo npm install -g create-react-app

Now, create a React app by type this command after back to the root of the project folder.

cd ..
create-react-app client

This command will create a new React app with the name client and this process can take minutes because all dependencies and modules also installing automatically. Next, go to the newly created app folder.

cd ./client

Now, run the React app for the first time using this command.

npm start

It will automatically open the default browser the point to <a href="http://localhost:3000" target="_blank">http://localhost:3000</a>, so the landing page should be like this.

9. Install and Configure Required Modules and Dependencies

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 react-apollo graphql-tag graphql --save

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

import ApolloClient from 'apollo-boost';
import { ApolloProvider } from 'react-apollo';

Instantiate ApolloClient module as a variable before the React.js class name.

const client = new ApolloClient();

Add the ApolloProvider to the root of React.js component.

ReactDOM.render(
    <ApolloProvider client={client}>
        <App />
    </ApolloProvider>, 
    document.getElementById('root')
);

10. Create React.js Router DOM

Before creating React Router DOM, first, we have to install the required NPM modules by typing these commands.

npm install --save react-router-dom
npm install --save-dev bootstrap

The React.js CRUD web application required pages to creating, show details and edit Book data. For that, type this commands to create those components.

mkdir src/components
touch src/components/Create.js
touch src/components/Show.js
touch src/components/Edit.js

Next, we will create routes for those components. Open and edit src/index.js then add these imports.

import { BrowserRouter as Router, Route } from 'react-router-dom';
import '../node_modules/bootstrap/dist/css/bootstrap.min.css';
import Edit from './components/Edit';
import Create from './components/Create';
import Show from './components/Show';

Add React Router to the ReactDOM render.

ReactDOM.render(
    <ApolloProvider client={client}>
        <App />
    </ApolloProvider>, 
    <Router>
        <div>
            <Route exact path='/' component={App} />
            <Route path='/edit/:id' component={Edit} />
            <Route path='/create' component={Create} />
            <Route path='/show/:id' component={Show} />
        </div>
    </Router>,
    document.getElementById('root')
);

As you see that Edit, Create and Show added as the separate component. Bootstrap also included in the import to make the views better.

11. Create a Component to Display List of Books

We need to add graphql-tag to use GraphQL query with React.js. Type this command to install it.

npm install graphql-tag --save

Next, open and edit client/App.js then replace all imports with these.

import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import './App.css';
import gql from 'graphql-tag';
import { Query } from 'react-apollo';

Declare a constant before the class name for the query.

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

Replace all render function contents with these.

render() {
    return (
      <Query pollInterval={500} query={GET_BOOKS}>
        {({ loading, error, data }) => {
          if (loading) return 'Loading...';
          if (error) return `Error! ${error.message}`;

          return (
            <div className="container">
              <div className="panel panel-default">
                <div className="panel-heading">
                  <h3 className="panel-title">
                    LIST OF BOOKS
                  </h3>
                  <h4><Link to="/create">Add Book</Link></h4>
                </div>
                <div className="panel-body">
                  <table className="table table-stripe">
                    <thead>
                      <tr>
                        <th>Title</th>
                        <th>Author</th>
                      </tr>
                    </thead>
                    <tbody>
                      {data.books.map((book, index) => (
                        <tr key={index}>
                          <td><Link to={`/show/${book._id}`}>{book.title}</Link></td>
                          <td>{book.title}</td>
                        </tr>
                      ))}
                    </tbody>
                  </table>
                </div>
              </div>
            </div>
          );
        }}
      </Query>
    );
}

12. Create a Component to Show and Delete Books

As you see in previous steps, it’s a link to show the details of the Book. For that, open and edit client/components/Show.js then add these imports.

import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import '../App.css';
import gql from 'graphql-tag';
import { Query, Mutation } from 'react-apollo';

Add the constants variables of query and mutation before the class name.

const GET_BOOK = gql`
    query book($bookId: String) {
        book(id: $bookId) {
            _id
            isbn
            title
            author
            description
            published_year
            publisher
            updated_date
        }
    }
`;

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

Add class with the name Show as below.

class Show extends Component {

  render() {
    return (
        <Query pollInterval={500} query={GET_BOOK} variables={{ bookId: this.props.match.params.id }}>
            {({ loading, error, data }) => {
                if (loading) return 'Loading...';
                if (error) return `Error! ${error.message}`;

                return (
                    <div className="container">
                        <div className="panel panel-default">
                            <div className="panel-heading">
                            <h4><Link to="/">Book List</Link></h4>
                                <h3 className="panel-title">
                                {data.book.title}
                                </h3>
                            </div>
                            <div className="panel-body">
                                <dl>
                                    <dt>ISBN:</dt>
                                    <dd>{data.book.isbn}</dd>
                                    <dt>Author:</dt>
                                    <dd>{data.book.author}</dd>
                                    <dt>Description:</dt>
                                    <dd>{data.book.description}</dd>
                                    <dt>Published Year:</dt>
                                    <dd>{data.book.published_year}</dd>
                                    <dt>Publisher:</dt>
                                    <dd>{data.book.publisher}</dd>
                                    <dt>Updated:</dt>
                                    <dd>{data.book.updated_date}</dd>
                                </dl>
                                <Mutation mutation={DELETE_BOOK} key={data.book._id} onCompleted={() => this.props.history.push('/')}>
                                    {(removeBook, { loading, error }) => (
                                        <div>
                                            <form
                                                onSubmit={e => {
                                                    e.preventDefault();
                                                    removeBook({ variables: { id: data.book._id } });
                                                }}>
                                                <Link to={`/edit/${data.book._id}`} className="btn btn-success">Edit</Link>&nbsp;
                                                <button type="submit" className="btn btn-danger">Delete</button>
                                            </form>
                                        {loading && <p>Loading...</p>}
                                        {error && <p>Error :( Please try again</p>}
                                        </div>
                                    )}
                                </Mutation>
                            </div>
                        </div>
                    </div>
                );
            }}
        </Query>
    );
  }
}

Finally, export this class name.

export default Show;

13. Create a Component to Add a New Book

To add a new Book, open and edit client/components/Create.js then add these imports.

import React, { Component } from 'react';
import gql from "graphql-tag";
import { Mutation } from "react-apollo";
import { Link } from 'react-router-dom';

Create a constant variable for the mutation.

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

Add a class with its contents like below.

class Create extends Component {

    render() {
      let isbn, title, author, description, published_year, publisher;
      return (
        <Mutation mutation={ADD_BOOK} onCompleted={() => this.props.history.push('/')}>
            {(addBook, { loading, error }) => (
                <div className="container">
                    <div className="panel panel-default">
                        <div className="panel-heading">
                            <h3 className="panel-title">
                                ADD BOOK
                            </h3>
                        </div>
                        <div className="panel-body">
                            <h4><Link to="/" className="btn btn-primary">Book List</Link></h4>
                            <form onSubmit={e => {
                                e.preventDefault();
                                addBook({ variables: { isbn: isbn.value, title: title.value, author: author.value, description: description.value, publisher: publisher.value, published_year: parseInt(published_year.value) } });
                                isbn.value = "";
                                title.value = "";
                                author.value = "";
                                description.value = "";
                                publisher.value = null;
                                published_year.value = "";
                            }}>
                                <div className="form-group">
                                    <label htmlFor="isbn">ISBN:</label>
                                    <input type="text" className="form-control" name="isbn" ref={node => {
                                        isbn = node;
                                    }} placeholder="ISBN" />
                                </div>
                                <div className="form-group">
                                    <label htmlFor="title">Title:</label>
                                    <input type="text" className="form-control" name="title" ref={node => {
                                        title = node;
                                    }} placeholder="Title" />
                                </div>
                                <div className="form-group">
                                    <label htmlFor="author">Author:</label>
                                    <input type="text" className="form-control" name="author" ref={node => {
                                        author = node;
                                    }} placeholder="Author" />
                                </div>
                                <div className="form-group">
                                    <label htmlFor="description">Description:</label>
                                    <textarea className="form-control" name="description" ref={node => {
                                        description = node;
                                    }} placeholder="Description" cols="80" rows="3" />
                                </div>
                                <div className="form-group">
                                    <label htmlFor="author">Publisher:</label>
                                    <input type="text" className="form-control" name="publisher" ref={node => {
                                        publisher = node;
                                    }} placeholder="Publisher" />
                                </div>
                                <div className="form-group">
                                    <label htmlFor="author">Published Year:</label>
                                    <input type="number" className="form-control" name="published_year" ref={node => {
                                        published_year = node;
                                    }} placeholder="Published Year" />
                                </div>
                                <button type="submit" className="btn btn-success">Submit</button>
                            </form>
                            {loading && <p>Loading...</p>}
                            {error && <p>Error :( Please try again</p>}
                        </div>
                    </div>
                </div>
            )}
        </Mutation>
      );
    }
}

Finally, export this class name.

export default Create;

14. Create a Component to Edit a Book

In the Show component, it’s a button that edit the Book. Now, we will create a component for edit a book. Open and edit client/components/Edit.js then add these imports.

import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import gql from "graphql-tag";
import { Query, Mutation } from "react-apollo";

Add a constant as a query to get the book data.

const GET_BOOK = gql`
    query book($bookId: String) {
        book(id: $bookId) {
            _id
            isbn
            title
            author
            description
            published_year
            publisher
            updated_date
        }
    }
`;

Add a constant as a mutation to update a book.

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

Add the class with the name Edit and it’s contents.

class Edit extends Component {

  render() {
    let isbn, title, author, description, published_year, publisher;
    return (
        <Query query={GET_BOOK} variables={{ bookId: this.props.match.params.id }}>
            {({ loading, error, data }) => {
                if (loading) return 'Loading...';
                if (error) return `Error! ${error.message}`;

                return (
                    <Mutation mutation={UPDATE_BOOK} key={data.book._id} onCompleted={() => this.props.history.push(`/`)}>
                        {(updateBook, { loading, error }) => (
                            <div className="container">
                                <div className="panel panel-default">
                                    <div className="panel-heading">
                                        <h3 className="panel-title">
                                            EDIT BOOK
                                        </h3>
                                    </div>
                                    <div className="panel-body">
                                        <h4><Link to="/" className="btn btn-primary">Book List</Link></h4>
                                        <form onSubmit={e => {
                                            e.preventDefault();
                                            updateBook({ variables: { id: data.book._id, isbn: isbn.value, title: title.value, author: author.value, description: description.value, publisher: publisher.value, published_year: parseInt(published_year.value) } });
                                            isbn.value = "";
                                            title.value = "";
                                            author.value = "";
                                            description.value = "";
                                            publisher.value = null;
                                            published_year.value = "";
                                        }}>
                                            <div className="form-group">
                                                <label htmlFor="isbn">ISBN:</label>
                                                <input type="text" className="form-control" name="isbn" ref={node => {
                                                    isbn = node;
                                                }} placeholder="ISBN" defaultValue={data.book.isbn} />
                                            </div>
                                            <div className="form-group">
                                                <label htmlFor="title">Title:</label>
                                                <input type="text" className="form-control" name="title" ref={node => {
                                                    title = node;
                                                }} placeholder="Title" defaultValue={data.book.title} />
                                            </div>
                                            <div className="form-group">
                                                <label htmlFor="author">Author:</label>
                                                <input type="text" className="form-control" name="author" ref={node => {
                                                    author = node;
                                                }} placeholder="Author" defaultValue={data.book.author} />
                                            </div>
                                            <div className="form-group">
                                                <label htmlFor="description">Description:</label>
                                                <textarea className="form-control" name="description" ref={node => {
                                                    description = node;
                                                }} placeholder="Description" cols="80" rows="3" defaultValue={data.book.description} />
                                            </div>
                                            <div className="form-group">
                                                <label htmlFor="author">Publisher:</label>
                                                <input type="text" className="form-control" name="publisher" ref={node => {
                                                    publisher = node;
                                                }} placeholder="Publisher" defaultValue={data.book.publisher} />
                                            </div>
                                            <div className="form-group">
                                                <label htmlFor="author">Published Year:</label>
                                                <input type="number" className="form-control" name="published_year" ref={node => {
                                                    published_year = node;
                                                }} placeholder="Published Year" defaultValue={data.book.published_year} />
                                            </div>
                                            <button type="submit" className="btn btn-success">Submit</button>
                                        </form>
                                        {loading && <p>Loading...</p>}
                                        {error && <p>Error :( Please try again</p>}
                                    </div>
                                </div>
                            </div>
                        )}
                    </Mutation>
                );
            }}
        </Query>
    );
  }
}

Finally, export the class name.

export default Edit;

15. Run and Test GraphQL CRUD from the React.js Application

Before running the whole application, make sure you have run the MongoDB server. To run the MongoDB server manually, type this command in the new Terminal tab.

mongod

Open the new terminal tab then type this command inside the project folder.

cd server
nodemon

Open the new terminal tab then type this command inside the project folder.

cd client
npm start

If asking to use a different port, just type Y. Now, the browser will automatically open then show the React.js and GraphQL application like these.

That it's, the Node, Express, React.js, Graphql, and MongoDB CRUD Web Application. You can find the full source code in our GitHub.

Thanks for reading ❤