Learn to Build a GraphQL Server with MERN

Learn to Build a GraphQL Server with MERN

In this GraphQL Server tutorial series, we will implement a GraphQL server with MERN that using Node and Express to communicate to a MongoDB Database and fetch and persist data to a social media app backend.

1. GraphQL Server (MERN + GraphQL): Intro & Demo

In this series, we will implement a GraphQL server that using Node and Express to communicate to a MongoDB Database and fetch and persist data to a social media app backend.

2. GraphQL Server (MERNG): Register & Login Users

We write mutations for registering new users and loggin them in and issuing authentication JWT tokens for them.

Full code: https://github.com/hidjou/classsed-graphql-mern-apollo/tree/class2

3. GraphQL Server (MERNG): Authentication Middleware & Create/Delete Posts

In this class we create a helper function to check if the user is authenticated and use it as middleware on queries/mutations that require the user to be logged in, and write mutations for creating and deleting posts.

Full code: https://github.com/hidjou/classsed-graphql-mern-apollo/tree/class3

4. GraphQL Server (MERNG): Likes & Comments (Finishing up)

In this class we implement functionality for posting/deleting comments and for liking/unliking posts, this is the last video for this series, after this we'll start to work on the react front-end.

Angular 9 Tutorial: Learn to Build a CRUD Angular App Quickly

What's new in Bootstrap 5 and when Bootstrap 5 release date?

What’s new in HTML6

How to Build Progressive Web Apps (PWA) using Angular 9

What is new features in Javascript ES2020 ECMAScript 2020

Node, Express, Angular 7, GraphQL and MongoDB CRUD Web App

Node, Express, Angular 7, GraphQL and MongoDB CRUD Web App

In this tutorial, we will go to the walkthrough of building GraphQL query language API for communication between Node-Express-MongoDB on Server side and Angular 7 on the Client side.

In this tutorial, we will go to the walkthrough of building GraphQL query language API for communication between Node-Express-MongoDB on Server side and Angular 7 on the Client side.

The comprehensive step by step tutorial on building CRUD (Create, Read, Update, Delete) Web Application using Node.js, Express.js, Angular 7, MongoDB and GraphQL. This is our first tutorial that using GraphQL, you can find more reference and guide on their official site.

On the server side, we are using Express-Graphql modules and it’s dependencies. For the client side, we are using Apollo Angular modules and dependencies.

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 Angular 7 Application
  • Install and Configure Required Modules and Dependencies
  • Create Routes for Navigation between Angular Pages/Component
  • Display List of Books using Angular 7 Material
  • Show and Delete Books
  • Add a New Book using Angular 7 Material
  • Edit a Book using Angular 7 Material
  • Run and Test GraphQL CRUD from the Angular 7 Application

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

We assume that you have installed Node.js. Now, we need to check the Node.js and NPM versions. Open the terminal or Node command line then type this commands.

node -v
v8.12.0
npm -v
6.4.1

That’s the Node.js and NPM version that we are using. Now, you can go to the main steps.

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-graphql

Go to the newly created directory.

cd ./node-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 represent 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 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/bookSchema');
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.

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. Create Angular 7 Application

Before creating an Angular 7 application, we have to install Angular 7 CLI first. Type this command to install it.

sudo npm install -g @angular/cli

Next, create a new Angular 7 Web Application using this Angular CLI command at the root of this project folder.

ng new client

If you get the question like below, choose Yes and SCSS (or whatever you like to choose).

? Would you like to add Angular routing? Yes
? Which stylesheet format would you like to use? SCSS

Next, go to the newly created Angular 7 project folder.

cd client

Type this command to run the Angular 7 application using this command.

ng serve

Open your browser then go to this address localhost:4200 to check if Angular 7 created successfully.

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 --save apollo-angular apollo-angular-link-http apollo-link apollo-client apollo-cache-inmemory graphql-tag graphql

Next, open and edit client/src/app/app.module.ts then add these imports.

import { HttpClientModule } from '@angular/common/http';
import { ApolloModule, Apollo } from 'apollo-angular';
import { HttpLinkModule, HttpLink } from 'apollo-angular-link-http';

Add these modules to the @NgModule imports.

imports: [
  ...,
  HttpClientModule,
  ApolloModule,
  HttpLinkModule,
  ...
],

Create a constructor inside class AppModule then inject above modules and create a connection to the GraphQL in the Express.js server.

export class AppModule {
  constructor(
    apollo: Apollo,
    httpLink: HttpLink
  ) {
     apollo.create({
      link: httpLink.create({ uri: 'http://localhost:3000/graphql'}),
      cache: new InMemoryCache()
    });
  }
}

10. Create Routes for Navigation between Angular Pages/Component

The Angular 7 routes already added when we create new Angular 7 application in the previous step. Before configuring the routes, type this command to create a new Angular 7 components.

ng g component books
ng g component books/detail
ng g component books/add
ng g component books/edit

Open client/src/app/app.module.ts then you will see those components imported and declared in @NgModule declarations. Next, open and edit src/app/app-routing.module.ts then add these imports.

import { BooksComponent } from './books/books.component';
import { DetailComponent } from './books/detail/detail.component';
import { AddComponent } from './books/add/add.component';
import { EditComponent } from './books/edit/edit.component';

Add these arrays to the existing empty array of routes constant.

const routes: Routes = [
  {
    path: 'books',
    component: BooksComponent,
    data: { title: 'List of Books' }
  },
  {
    path: 'books/detail/:id',
    component: DetailComponent,
    data: { title: 'Book Details' }
  },
  {
    path: 'books/add',
    component: AddComponent,
    data: { title: 'Add Book' }
  },
  {
    path: 'books/edit/:id',
    component: EditComponent,
    data: { title: 'Edit Book' }
  },
  { path: '',
    redirectTo: '/books',
    pathMatch: 'full'
  }
];

Open and edit client/src/app/app.component.html and you will see the existing router outlet. Next, modify this HTML page to fit the CRUD page.

<!--The content below is only a placeholder and can be replaced.-->
<div style="text-align:center">
  <h1>
    Welcome to {{ title }}!
  </h1>
  <img width="300" alt="Angular Logo" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg==">
</div>

<div class="container">
  <router-outlet></router-outlet>
</div>

Finally, open and edit src/app/app.component.scss then replace all SASS codes with this.

.container {
  padding: 20px;
}

11. Display List of Books using Angular 7 Material

We will be using Angular 7 Material as UI/UX component. First, we have to install these modules to the Angular 7 application. Type this Angular 7 Schema to install it.

ng add @angular/material

If there are questions like below, just use the default answer.

? Enter a prebuilt theme name, or "custom" for a custom theme: purple-green
? Set up HammerJS for gesture recognition? Yes
? Set up browser animations for Angular Material? Yes

We will register all required Angular Material components or modules to client/src/app/app.module.ts. Open and edit that file then add this imports.

import {
  MatInputModule,
  MatPaginatorModule,
  MatProgressSpinnerModule,
  MatSortModule,
  MatTableModule,
  MatIconModule,
  MatButtonModule,
  MatCardModule,
  MatFormFieldModule } from "@angular/material";

Of course we will use Angular 7 Reactive Form module, for that, modify FormsModule import to add ReactiveFormsModule.

import { FormsModule, ReactiveFormsModule } from '@angular/forms';

Register the above modules to @NgModule imports array.

imports: [
  ...
  ReactiveFormsModule,
  BrowserAnimationsModule,
  MatInputModule,
  MatTableModule,
  MatPaginatorModule,
  MatSortModule,
  MatProgressSpinnerModule,
  MatIconModule,
  MatButtonModule,
  MatCardModule,
  MatFormFieldModule
],

Next, to display a list of Books. Open and edit client/src/app/books/books.component.ts that previously generated then add these imports.

import { Apollo } from 'apollo-angular';
import gql from 'graphql-tag';
import { Book } from './book';

Declare all required variables for hold response, data, Angular Material table column and loading spinner control.

displayedColumns: string[] = ['title', 'author'];
data: Book[] = [];
resp: any = {};
isLoadingResults = true;

Inject the Apollo Angular module to the constructor.

constructor(private apollo: Apollo) {
}

Add a gql query inside ngOnInit() function.

ngOnInit() {
  this.apollo.query({
    query: gql `{ books { _id, title, author } }`
  }).subscribe(res => {
    this.resp = res;
    this.data = this.resp.data.books;
    console.log(this.data);
    this.isLoadingResults = false;
  });
}

Next, open and edit client/src/app/books/books.component.html then replace all HTML tags with this.

<div class="example-container mat-elevation-z8">
  <div class="example-loading-shade"
       *ngIf="isLoadingResults">
    <mat-spinner *ngIf="isLoadingResults"></mat-spinner>
  </div>
  <div class="button-row">
    <a mat-flat-button color="primary" [routerLink]="['/books/add']"><mat-icon>add</mat-icon></a>
  </div>
  <div class="mat-elevation-z8">
    <table mat-table [dataSource]="data" class="example-table"
           matSort matSortActive="title" matSortDisableClear matSortDirection="asc">

      <!-- Product Name Column -->
      <ng-container matColumnDef="title">
        <th mat-header-cell *matHeaderCellDef>Title</th>
        <td mat-cell *matCellDef="let row">{{row.title}}</td>
      </ng-container>

      <!-- Product Price Column -->
      <ng-container matColumnDef="author">
        <th mat-header-cell *matHeaderCellDef>Author</th>
        <td mat-cell *matCellDef="let row">{{row.author}}</td>
      </ng-container>

      <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
      <tr mat-row *matRowDef="let row; columns: displayedColumns;" [routerLink]="['/books/detail/', row._id]"></tr>
    </table>
  </div>
</div>

Finally, add some styles for this page by open and edit client/src/app/books/books.component.scss then add these lines of SCSS codes.

/* Structure */
.example-container {
  position: relative;
  padding: 5px;
}

.example-table-container {
  position: relative;
  max-height: 400px;
  overflow: auto;
}

table {
  width: 100%;
}

.example-loading-shade {
  position: absolute;
  top: 0;
  left: 0;
  bottom: 56px;
  right: 0;
  background: rgba(0, 0, 0, 0.15);
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
}

.example-rate-limit-reached {
  color: #980000;
  max-width: 360px;
  text-align: center;
}

/* Column Widths */
.mat-column-number,
.mat-column-state {
  max-width: 64px;
}

.mat-column-created {
  max-width: 124px;
}

.mat-flat-button {
  margin: 5px;
}

12. Show and Delete Books

On the list of Books page we have a clickable row that can redirect to the show details page. Next, open and edit client/src/app/books/detail/detail.component.ts then add these imports.

import { ActivatedRoute, Router } from '@angular/router';
import { Apollo, QueryRef } from 'apollo-angular';
import gql from 'graphql-tag';
import { Book } from '../book';

Declare a constant variable before the class name for query and delete a book by ID.

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

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

Next, declare all required variables before the constructor.

book: Book = { id: '', isbn: '', title: '', author: '', description: '', publisher: '', publishedYear: null, updatedDate: null };
isLoadingResults = true;
resp: any = {};
private query: QueryRef<any>;

Inject above imported modules to the constructor.

constructor(private apollo: Apollo, private router: Router, private route: ActivatedRoute) { }

Add a function for get a single Book data by ID.

getBookDetails() {
  const id = this.route.snapshot.params.id;
  this.query = this.apollo.watchQuery({
    query: bookQuery,
    variables: { bookId: id }
  });

  this.query.valueChanges.subscribe(res => {
    this.book = res.data.book;
    console.log(this.book);
    this.isLoadingResults = false;
  });
}

Call that function from ngOnInit function.

ngOnInit() {
  this.getBookDetails();
}

Add a function for delete a book by ID.

deleteBook() {
  this.isLoadingResults = true;
  const bookId = this.route.snapshot.params.id;
  this.apollo.mutate({
    mutation: deleteBook,
    variables: {
      id: bookId
    }
  }).subscribe(({ data }) => {
    console.log('got data', data);
    this.isLoadingResults = false;
    this.router.navigate(['/books']);
  }, (error) => {
    console.log('there was an error sending the query', error);
    this.isLoadingResults = false;
  });
}

For the view, open and edit client/src/app/books/detail/detail.component.html then replace all HTML tags with these lines of HTML tags.

<div class="example-container mat-elevation-z8">
  <div class="example-loading-shade"
       *ngIf="isLoadingResults">
    <mat-spinner *ngIf="isLoadingResults"></mat-spinner>
  </div>
  <div class="button-row">
    <a mat-flat-button color="primary" [routerLink]="['/books']"><mat-icon>list</mat-icon></a>
  </div>
  <mat-card class="example-card">
    <mat-card-header>
      <mat-card-title><h2>{{book.title}}</h2></mat-card-title>
      <mat-card-subtitle>{{book.author}}</mat-card-subtitle>
    </mat-card-header>
    <mat-card-content>
      <dl>
        <dt>ISBN:</dt>
        <dd>{{book.isbn}}</dd>
        <dt>Description:</dt>
        <dd>{{book.description}}</dd>
        <dt>Publisher:</dt>
        <dd>{{book.publisher}}</dd>
        <dt>Published Year:</dt>
        <dd>{{book.published_year}}</dd>
        <dt>Update Date:</dt>
        <dd>{{book.updated_date}}</dd>
      </dl>
    </mat-card-content>
    <mat-card-actions>
      <a mat-flat-button color="primary" [routerLink]="['/books/edit', book._id]"><mat-icon>edit</mat-icon></a>
      <a mat-flat-button color="warn" (click)="deleteBook(book._id)"><mat-icon>delete</mat-icon></a>
    </mat-card-actions>
  </mat-card>
</div>

To adjust the style, open and edit client/src/app/books/detail/detail.component.scss then add these lines of SCSS codes.

/* Structure */
.example-container {
  position: relative;
  padding: 5px;
}

.example-loading-shade {
  position: absolute;
  top: 0;
  left: 0;
  bottom: 56px;
  right: 0;
  background: rgba(0, 0, 0, 0.15);
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
}

.mat-flat-button {
  margin: 5px;
}

13. Add a New Book using Angular 7 Material

In the list of Book we have an Add button that will redirect to the Add Page. Next, open and edit client/src/app/books/add/add.component.ts then add these imports.

import { Router } from '@angular/router';
import { FormBuilder, FormGroup, NgForm, Validators } from '@angular/forms';
import { Apollo } from 'apollo-angular';
import gql from 'graphql-tag';

Add a constant of gql query after the imports for submitting or post a new Book data.

const submitBook = 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
    }
  }
`;

Declare all required variables before the constructor.

book: any = { isbn: '', title: '', author: '', description: '', publisher: '', publishedYear: null, updatedDate: null };
isLoadingResults = false;
resp: any = {};
bookForm: FormGroup;
isbn = '';
title = '';
author = '';
description = '';
publisher = '';
publishedYear: number = null;

Inject above imported modules to the constructor.

constructor(
  private apollo: Apollo,
  private router: Router,
  private formBuilder: FormBuilder
) { }

Initialize Angular 7 form group inside ngOnInit function.

ngOnInit() {
  this.bookForm = this.formBuilder.group({
    isbn : [null, Validators.required],
    title : [null, Validators.required],
    author : [null, Validators.required],
    description : [null, Validators.required],
    publisher : [null, Validators.required],
    publishedYear : [null, Validators.required]
  });
}

Add a function to get the form controls from the form group.

get f() {
  return this.bookForm.controls;
}

Add a function to submit or post a new book data.

onSubmit(form: NgForm) {
  this.isLoadingResults = true;
  const bookData = form.value;
  this.apollo.mutate({
    mutation: submitBook,
    variables: {
      isbn: bookData.isbn,
      title: bookData.title,
      author: bookData.author,
      description: bookData.description,
      publisher: bookData.publisher,
      published_year: bookData.publishedYear
    }
  }).subscribe(({ data }) => {
    console.log('got data', data);
    this.isLoadingResults = false;
    this.router.navigate(['/books/detail/', data.addBook._id]);
  }, (error) => {
    console.log('there was an error sending the query', error);
    this.isLoadingResults = false;
  });
}

Next, open and edit client/src/app/books/add/add.component.html then replace all HTML tags with this.

<div class="example-container mat-elevation-z8">
  <div class="example-loading-shade"
       *ngIf="isLoadingResults">
    <mat-spinner *ngIf="isLoadingResults"></mat-spinner>
  </div>
  <div class="button-row">
    <a mat-flat-button color="primary" [routerLink]="['/books']"><mat-icon>list</mat-icon></a>
  </div>
  <mat-card class="example-card">
    <form [formGroup]="bookForm" #f="ngForm" (ngSubmit)="onSubmit(f)" novalidate>
      <mat-form-field class="example-full-width">
        <input matInput placeholder="ISBN" formControlName="isbn"
               [errorStateMatcher]="matcher">
        <mat-error>
          <span *ngIf="!bookForm.get('isbn').valid && bookForm.get('isbn').touched">Please enter ISBN</span>
        </mat-error>
      </mat-form-field>
      <mat-form-field class="example-full-width">
        <input matInput placeholder="Title" formControlName="title"
               [errorStateMatcher]="matcher">
        <mat-error>
          <span *ngIf="!bookForm.get('title').valid && bookForm.get('title').touched">Please enter Title</span>
        </mat-error>
      </mat-form-field>
      <mat-form-field class="example-full-width">
        <input matInput placeholder="Author" formControlName="author"
               [errorStateMatcher]="matcher">
        <mat-error>
          <span *ngIf="!bookForm.get('author').valid && bookForm.get('author').touched">Please enter Author</span>
        </mat-error>
      </mat-form-field>
      <mat-form-field class="example-full-width">
        <textarea matInput placeholder="Description" formControlName="description"
               [errorStateMatcher]="matcher"></textarea>
        <mat-error>
          <span *ngIf="!bookForm.get('description').valid && bookForm.get('description').touched">Please enter Description</span>
        </mat-error>
      </mat-form-field>
      <mat-form-field class="example-full-width">
        <input matInput placeholder="Publisher" formControlName="publisher"
               [errorStateMatcher]="matcher">
        <mat-error>
          <span *ngIf="!bookForm.get('publisher').valid && bookForm.get('publisher').touched">Please enter Publisher</span>
        </mat-error>
      </mat-form-field>
      <mat-form-field class="example-full-width">
        <input matInput placeholder="Published Year" type="number" formControlName="publishedYear"
               [errorStateMatcher]="matcher">
        <mat-error>
          <span *ngIf="!bookForm.get('publishedYear').valid && bookForm.get('publishedYear').touched">Please enter Published Year</span>
        </mat-error>
      </mat-form-field>
      <div class="button-row">
        <button type="submit" [disabled]="!bookForm.valid" mat-flat-button color="primary"><mat-icon>save</mat-icon></button>
      </div>
    </form>
  </mat-card>
</div>

Give a litle style by open and edit client/src/app/books/add/add.component.scss then add this lines of SCSS codes.

/* Structure */
.example-container {
  position: relative;
  padding: 5px;
}

.example-form {
  min-width: 150px;
  max-width: 500px;
  width: 100%;
}

.example-full-width {
  width: 100%;
}

.example-full-width:nth-last-child() {
  margin-bottom: 10px;
}

.button-row {
  margin: 10px 0;
}

.mat-flat-button {
  margin: 5px;
}

14. Edit a Book using Angular 7 Material

We have put an edit button inside the Book Detail component for a redirect to Edit page. Now, open and edit client/src/app/books/edit/edit.component.ts then add these imports.

import { ActivatedRoute, Router } from '@angular/router';
import { FormBuilder, FormGroup, NgForm, Validators } from '@angular/forms';
import { Apollo, QueryRef } from 'apollo-angular';
import gql from 'graphql-tag';

Add gql query before the class name for getting single Book by ID and submit book data.

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

const submitBook = 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 all required variables before the constructor.

book: any = { _id: '', isbn: '', title: '', author: '', description: '', publisher: '', publishedYear: null, updatedDate: null };
isLoadingResults = true;
resp: any = {};
private query: QueryRef<any>;
bookForm: FormGroup;
id = '';
isbn = '';
title = '';
author = '';
description = '';
publisher = '';
publishedYear: number = null;

Inject above imported modules to the constructor.

constructor(
  private apollo: Apollo,
  private route: ActivatedRoute,
  private router: Router,
  private formBuilder: FormBuilder) { }

Initialize the Angular 7 form group to the ngOnInit function.

ngOnInit() {
  this.bookForm = this.formBuilder.group({
    isbn : [null, Validators.required],
    title : [null, Validators.required],
    author : [null, Validators.required],
    description : [null, Validators.required],
    publisher : [null, Validators.required],
    publishedYear : [null, Validators.required]
  });
}

Add a function to get the form controls from the form group.

get f() {
  return this.bookForm.controls;
}

Add a function to get a single book data by ID.

getBookDetails() {
  const id = this.route.snapshot.params.id;
  this.query = this.apollo.watchQuery({
    query: bookQuery,
    variables: { bookId: id }
  });

  this.query.valueChanges.subscribe(res => {
    this.book = res.data.book;
    console.log(this.book);
    this.id = this.book._id;
    this.isLoadingResults = false;
    this.bookForm.setValue({
      isbn: this.book.isbn,
      title: this.book.title,
      author: this.book.author,
      description: this.book.description,
      publisher: this.book.publisher,
      publishedYear: this.book.published_year
    });
  });
}

Call that function from the ngOnInit function.

ngOnInit() {
  ...
  this.getBookDetails();
}

Add a function for submitting the Book data to the GraphQL.

onSubmit(form: NgForm) {
  this.isLoadingResults = true;
  console.log(this.id);
  const bookData = form.value;
  this.apollo.mutate({
    mutation: submitBook,
    variables: {
      id: this.id,
      isbn: bookData.isbn,
      title: bookData.title,
      author: bookData.author,
      description: bookData.description,
      publisher: bookData.publisher,
      published_year: bookData.publishedYear
    }
  }).subscribe(({ data }) => {
    console.log('got data', data);
    this.isLoadingResults = false;
  }, (error) => {
    console.log('there was an error sending the query', error);
    this.isLoadingResults = false;
  });
}

Add a function to enter the Book details after click a Details button.

bookDetails() {
  this.router.navigate(['/books/detail/', this.id]);
}

Next, open and edit client/src/app/books/edit/edit.component.html then replace all HTML tags with this.

<div class="example-container mat-elevation-z8">
  <div class="example-loading-shade"
       *ngIf="isLoadingResults">
    <mat-spinner *ngIf="isLoadingResults"></mat-spinner>
  </div>
  <div class="button-row">
    <a mat-flat-button color="primary" (click)="bookDetails()"><mat-icon>info</mat-icon></a>
  </div>
  <mat-card class="example-card">
    <form [formGroup]="bookForm" #f="ngForm" (ngSubmit)="onSubmit(f)" novalidate>
      <mat-form-field class="example-full-width">
        <input matInput placeholder="ISBN" formControlName="isbn"
               [errorStateMatcher]="matcher">
        <mat-error>
          <span *ngIf="!bookForm.get('isbn').valid && bookForm.get('isbn').touched">Please enter ISBN</span>
        </mat-error>
      </mat-form-field>
      <mat-form-field class="example-full-width">
        <input matInput placeholder="Title" formControlName="title"
               [errorStateMatcher]="matcher">
        <mat-error>
          <span *ngIf="!bookForm.get('title').valid && bookForm.get('title').touched">Please enter Title</span>
        </mat-error>
      </mat-form-field>
      <mat-form-field class="example-full-width">
        <input matInput placeholder="Author" formControlName="author"
               [errorStateMatcher]="matcher">
        <mat-error>
          <span *ngIf="!bookForm.get('author').valid && bookForm.get('author').touched">Please enter Author</span>
        </mat-error>
      </mat-form-field>
      <mat-form-field class="example-full-width">
        <textarea matInput placeholder="Description" formControlName="description"
               [errorStateMatcher]="matcher"></textarea>
        <mat-error>
          <span *ngIf="!bookForm.get('description').valid && bookForm.get('description').touched">Please enter Description</span>
        </mat-error>
      </mat-form-field>
      <mat-form-field class="example-full-width">
        <input matInput placeholder="Publisher" formControlName="publisher"
               [errorStateMatcher]="matcher">
        <mat-error>
          <span *ngIf="!bookForm.get('publisher').valid && bookForm.get('publisher').touched">Please enter Publisher</span>
        </mat-error>
      </mat-form-field>
      <mat-form-field class="example-full-width">
        <input matInput placeholder="Published Year" type="number" formControlName="publishedYear"
               [errorStateMatcher]="matcher">
        <mat-error>
          <span *ngIf="!bookForm.get('publishedYear').valid && bookForm.get('publishedYear').touched">Please enter Published Year</span>
        </mat-error>
      </mat-form-field>
      <div class="button-row">
        <button type="submit" [disabled]="!bookForm.valid" mat-flat-button color="primary"><mat-icon>save</mat-icon></button>
      </div>
    </form>
  </mat-card>
</div>

For styling, open and edit client/src/app/books/edit/edit.component.scss then add these lines of SCSS codes.

/* Structure */
.example-container {
  position: relative;
  padding: 5px;
}

.example-form {
  min-width: 150px;
  max-width: 500px;
  width: 100%;
}

.example-full-width {
  width: 100%;
}

.example-full-width:nth-last-child() {
  margin-bottom: 10px;
}

.button-row {
  margin: 10px 0;
}

.mat-flat-button {
  margin: 5px;
}

15. Run and Test GraphQL CRUD from the Angular 7 Application

Before the test, the GraphQL CRUD from the Angular 7 Application, just makes sure that you have run MongoDB server and Express.js server. If not yet, run those servers in different Terminal tabs. Next, run the Angular 7 application from the different terminal tabs.

ng serve

In the browser go to this URL localhost:4200 and here the whole application looks like.

That it’s, we have finished the Node, Express, Angular 7, GraphQL and MongoDB CRUD Web App. If you can’t follow the steps of the tutorial, you can compare it with the working source code from our GitHub.

Thanks for reading ❤

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 ❤

How to Use Express.js, Node.js and MongoDB.js

How to Use Express.js, Node.js and MongoDB.js

In this post, I will show you how to use Express.js, Node.js and MongoDB.js. We will be creating a very simple Node application, that will allow users to input data that they want to store in a MongoDB database. It will also show all items that have been entered into the database.

In this post, I will show you how to use Express.js, Node.js and MongoDB.js. We will be creating a very simple Node application, that will allow users to input data that they want to store in a MongoDB database. It will also show all items that have been entered into the database.

Creating a Node Application

To get started I would recommend creating a new database that will contain our application. For this demo I am creating a directory called node-demo. After creating the directory you will need to change into that directory.

mkdir node-demo
cd node-demo

Once we are in the directory we will need to create an application and we can do this by running the command
npm init

This will ask you a series of questions. Here are the answers I gave to the prompts.

The first step is to create a file that will contain our code for our Node.js server.

touch app.js

In our app.js we are going to add the following code to build a very simple Node.js Application.

var express = require("express");
var app = express();
var port = 3000;
 
app.get("/", (req, res) => {
&nbsp;&nbsp;res.send("Hello World");
});
 
app.listen(port, () => {
  console.log("Server listening on port " + port);
});

What the code does is require the express.js application. It then creates app by calling express. We define our port to be 3000.

The app.use line will listen to requests from the browser and will return the text “Hello World” back to the browser.

The last line actually starts the server and tells it to listen on port 3000.

Installing Express

Our app.js required the Express.js module. We need to install express in order for this to work properly. Go to your terminal and enter this command.

npm install express --save

This command will install the express module into our package.json. The module is installed as a dependency in our package.json as shown below.

To test our application you can go to the terminal and enter the command

node app.js

Open up a browser and navigate to the url http://localhost:3000

You will see the following in your browser

Creating Website to Save Data to MongoDB Database

Instead of showing the text “Hello World” when people view your application, what we want to do is to show a place for user to save data to the database.

We are going to allow users to enter a first name and a last name that we will be saving in the database.

To do this we will need to create a basic HTML file. In your terminal enter the following command to create an index.html file.

touch index.html

In our index.html file we will be creating an input filed where users can input data that they want to have stored in the database. We will also need a button for users to click on that will add the data to the database.

Here is what our index.html file looks like.

<!DOCTYPE html>
<html>
  <head>
    <title>Intro to Node and MongoDB<title>
  <head>

  <body>
    <h1>Into to Node and MongoDB<&#47;h1>
    <form method="post" action="/addname">
      <label>Enter Your Name<&#47;label><br>
      <input type="text" name="firstName" placeholder="Enter first name..." required>
      <input type="text" name="lastName" placeholder="Enter last name..." required>
      <input type="submit" value="Add Name">
    </form>
  <body>
<html>

If you are familiar with HTML, you will not find anything unusual in our code for our index.html file. We are creating a form where users can input their first name and last name and then click an “Add Name” button.

The form will do a post call to the /addname endpoint. We will be talking about endpoints and post later in this tutorial.

Displaying our Website to Users

We were previously displaying the text “Hello World” to users when they visited our website. Now we want to display our html file that we created. To do this we will need to change the app.use line our our app.js file.

We will be using the sendFile command to show the index.html file. We will need to tell the server exactly where to find the index.html file. We can do that by using a node global call __dirname. The __dirname will provide the current directly where the command was run. We will then append the path to our index.html file.

The app.use lines will need to be changed to
app.use("/", (req, res) => {   res.sendFile(__dirname + "/index.html"); });

Once you have saved your app.js file, we can test it by going to terminal and running node app.js

Open your browser and navigate to “http://localhost:3000”. You will see the following

Connecting to the Database

Now we need to add our database to the application. We will be connecting to a MongoDB database. I am assuming that you already have MongoDB installed and running on your computer.

To connect to the MongoDB database we are going to use a module called Mongoose. We will need to install mongoose module just like we did with express. Go to your terminal and enter the following command.
npm install mongoose --save

This will install the mongoose model and add it as a dependency in our package.json.

Connecting to the Database

Now that we have the mongoose module installed, we need to connect to the database in our app.js file. MongoDB, by default, runs on port 27017. You connect to the database by telling it the location of the database and the name of the database.

In our app.js file after the line for the port and before the app.use line, enter the following two lines to get access to mongoose and to connect to the database. For the database, I am going to use “node-demo”.

var mongoose = require("mongoose"); mongoose.Promise = global.Promise; mongoose.connect("mongodb://localhost:27017/node-demo");

Creating a Database Schema

Once the user enters data in the input field and clicks the add button, we want the contents of the input field to be stored in the database. In order to know the format of the data in the database, we need to have a Schema.

For this tutorial, we will need a very simple Schema that has only two fields. I am going to call the field firstName and lastName. The data stored in both fields will be a String.

After connecting to the database in our app.js we need to define our Schema. Here are the lines you need to add to the app.js.
var nameSchema = new mongoose.Schema({   firstName: String,   lastNameName: String });

Once we have built our Schema, we need to create a model from it. I am going to call my model “DataInput”. Here is the line you will add next to create our mode.
var User = mongoose.model("User", nameSchema);

Creating RESTful API

Now that we have a connection to our database, we need to create the mechanism by which data will be added to the database. This is done through our REST API. We will need to create an endpoint that will be used to send data to our server. Once the server receives this data then it will store the data in the database.

An endpoint is a route that our server will be listening to to get data from the browser. We already have one route that we have created already in the application and that is the route that is listening at the endpoint “/” which is the homepage of our application.

HTTP Verbs in a REST API

The communication between the client(the browser) and the server is done through an HTTP verb. The most common HTTP verbs are
GET, PUT, POST, and DELETE.

The following table explains what each HTTP verb does.

HTTP Verb Operation
GET Read
POST Create
PUT Update
DELETE Delete

As you can see from these verbs, they form the basis of CRUD operations that I talked about previously.

Building a CRUD endpoint

If you remember, the form in our index.html file used a post method to call this endpoint. We will now create this endpoint.

In our previous endpoint we used a “GET” http verb to display the index.html file. We are going to do something very similar but instead of using “GET”, we are going to use “POST”. To get started this is what the framework of our endpoint will look like.

app.post("/addname", (req, res) => {
 
});
Express Middleware

To fill out the contents of our endpoint, we want to store the firstName and lastName entered by the user into the database. The values for firstName and lastName are in the body of the request that we send to the server. We want to capture that data, convert it to JSON and store it into the database.

Express.js version 4 removed all middleware. To parse the data in the body we will need to add middleware into our application to provide this functionality. We will be using the body-parser module. We need to install it, so in your terminal window enter the following command.

npm install body-parser --save

Once it is installed, we will need to require this module and configure it. The configuration will allow us to pass the data for firstName and lastName in the body to the server. It can also convert that data into JSON format. This will be handy because we can take this formatted data and save it directly into our database.

To add the body-parser middleware to our application and configure it, we can add the following lines directly after the line that sets our port.

var bodyParser = require('body-parser');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
Saving data to database

Mongoose provides a save function that will take a JSON object and store it in the database. Our body-parser middleware, will convert the user’s input into the JSON format for us.

To save the data into the database, we need to create a new instance of our model that we created early. We will pass into this instance the user’s input. Once we have it then we just need to enter the command “save”.

Mongoose will return a promise on a save to the database. A promise is what is returned when the save to the database completes. This save will either finish successfully or it will fail. A promise provides two methods that will handle both of these scenarios.

If this save to the database was successful it will return to the .then segment of the promise. In this case we want to send text back the user to let them know the data was saved to the database.

If it fails it will return to the .catch segment of the promise. In this case, we want to send text back to the user telling them the data was not saved to the database. It is best practice to also change the statusCode that is returned from the default 200 to a 400. A 400 statusCode signifies that the operation failed.

Now putting all of this together here is what our final endpoint will look like.

app.post("/addname", (req, res) => {
  var myData = new User(req.body);
  myData.save()
    .then(item => {
      res.send("item saved to database");
    })
    .catch(err => {
      res.status(400).send("unable to save to database");
    });
});
Testing our code

Save your code. Go to your terminal and enter the command node app.js to start our server. Open up your browser and navigate to the URL “http://localhost:3000”. You will see our index.html file displayed to you.

Make sure you have mongo running.

Enter your first name and last name in the input fields and then click the “Add Name” button. You should get back text that says the name has been saved to the database like below.

Access to Code

The final version of the code is available in my Github repo. To access the code click here. Thank you for reading !