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

Table of Contents:

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.



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`



Install and Configure Mongoose.js Modules for Accessing MongoDB

Why using Mongoose.js as MongoDB ODM? Because Mongoose provides a straight-forward, schema-based solution to model your application data. It includes built-in type casting, validation, query building, business logic hooks and more, out of the box. 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 these 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.



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);



Install GraphQL Modules and Dependencies

Why using GraphQL? GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more, makes it easier to evolve APIs over time, and enables powerful developer tools. Next, 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.



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});



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});



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` 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" } } }



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 `http://localhost:3000`, so the landing page should be like this.



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') );



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.



Create a React 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> ); }



Create a React 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> <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;



Create a React 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;



Create a React Component to Edit a Book

In the `Show` component, it's a button that edits 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;



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.

That just the basic. If you need more deep learning about MERN Stack, React.js or React Native you can take the following cheap course:

Thanks!