How to create a realtime app using Socket.io, React, Node & MongoDB

Ever wondered how real time apps are built? Ever noticed the importance and use cases of real time applications?

If you are curious about the above questions and need an answer, then this blog post is for you.

First, let’s identify a few use cases needing real time applications:

  1. Getting location updates for your cab on a map of a cab booking application.
  2. Getting new messages instantly on your favourite chatting application.
  3. Food order info update to the kitchen of your favourite restaurant.

These all are the common scenarios of our day to day lives where we can’t tolerate a delay in the updating of information and hence need real time communication.

Technologies which can be used for realtime communication are:

  1. Getting location updates for your cab on a map of a cab booking application.
  2. Getting new messages instantly on your favourite chatting application.
  3. Food order info update to the kitchen of your favourite restaurant.

More in-depth info about the above three technologies can be read here.

We will be learning to create a real time application by covering the following scenario.

Imagine you are sitting at your favourite restaurant and have a digital menu. You place the order and the kitchen gets updated regarding your order in real time. When the kitchen is done with the order, they update it in real time too.

Features in detail:

  1. Getting location updates for your cab on a map of a cab booking application.
  2. Getting new messages instantly on your favourite chatting application.
  3. Food order info update to the kitchen of your favourite restaurant.

A live demo of this scenario can be found here.

For better understanding, open it in different tabs/devices at the same time to see the data change in real time.

The source code is here. Feel free to make something innovative/useful on top of it.

So let’s get started.

Technology Stack:

Frontend: React.js, Reactstrap, Socket.io

Backend: Node.js (Express), MongoDB, Socket.io

Folder Structure:

/*
Go to the root directory in the source code and find out the below-mentioned files. This architecture helps in creating a big modular App.
*/
backend-my-app/ /* Backend code of the app */
 server.js       /* Socket and backend code resides here*/
 build/      /* Optional for deployment of Frontend Build */ 
 package.json /* Backend dependency */
 ...
public/
src/  /*      Frontend Sourcecode      */
 global/      /*   Components getting used everywhere   */
  header.css
  header.js     
 main/           
  Kitchen.js
  PlaceOrder.js
  UpdatePredicted.js
 App.js   /* Routing logic and component assembly part */
package.json /* Frontend dependency */ 
 ............

Explanation of source code:

Frontend:

git clone https://github.com/honey93/OrderKitchen.git
cd OrderKitchen
npm install
npm start

Packages used:

  1. Getting location updates for your cab on a map of a cab booking application.
  2. Getting new messages instantly on your favourite chatting application.
  3. Food order info update to the kitchen of your favourite restaurant.

App Component

Path: src/App.js

This component contains the main routing logic of the Frontend. This file is used in src/index.js inside the Browser Router Module. The below code demonstrates one of the approaches to keep your app modular.

import React, { Component } from "react";
import "./App.css";
import { Header } from "./global/header";
import { Switch, Route } from "react-router-dom";
import PlaceOrder from "./main/PlaceOrder";
import UpdatePredicted from "./main/UpdatePredicted";
import Kitchen from "./main/Kitchen";
/*The <Route> component is the main part of React Router. Anywhere that you want to only render content based on the location’s pathname, you should use a <Route> element. */
/* The Route component expects a path prop, which is a string that describes the pathname that the route matches */
/* The <Switch> will iterate over routes and only render the first one that matches the current pathname */
class App extends Component {
  render() {
    return (
      <div className="App">
        <Header />
        <Switch>
          <Route exact path="/" component={PlaceOrder} />
          <Route path="/updatepredicted" component={UpdatePredicted} />
          <Route path="/kitchen" component={Kitchen} />
        </Switch>
      </div>
    );
  }
}
export default App;

Header Component

Path: src/global/header.js

This component will be common and used across the sections like Place Order, Change Predicted, Kitchen. This approach helps avoid code duplication and keeps the application modular.

import React, { Component } from "react";
import { NavLink } from "react-router-dom";
import socketIOClient from "socket.io-client";
import "./header.css";
// The Header creates links that can be used to navigate
// between routes.
var socket;
class Header extends Component {
/* Creating a Socket client and exporting it at the end to be used across the Place Order, Kitchen, etc components*/
  constructor() {
    super();
    this.state = {
      endpoint: 'http://localhost:3001/'
    };
socket = socketIOClient(this.state.endpoint);
  }
render() {
    return (
      <header>
        <nav>
          <ul className="NavClass">
            <li>
              <NavLink exact to="/">
                Place Order
              </NavLink>
            </li>
            <li>
              <NavLink to="/updatepredicted">Change Predicted </NavLink>
            </li>
            <li>
              <NavLink to="/kitchen"> Kitchen </NavLink>
            </li  >
          </ul>
        </nav>
      </header>
    );
  }
}
export { Header, socket };

Kitchen Component

Path: src/main/Kitchen.js

The Kitchen Screen UI logic and html code resides in this component.

import React, { Component } from "react";
import { Button, Table, Container } from "reactstrap";
import { socket } from "../global/header";
import ReactHTMLTableToExcel from "react-html-table-to-excel";
class Kitchen extends Component {
  constructor() {
    super();
    this.state = {
      food_data: []
      // this is where we are connecting to with sockets,
    };
  }
getData = foodItems => {
    console.log(foodItems);
    this.setState({ food_data: foodItems });
  };
changeData = () => socket.emit("initial_data");
/*As soon as the component gets mounted ie in componentDidMount method, firing the initial_data event to get the data to initialize the Kitchen Dashboard */
/* Adding change_data listener for listening to any changes made by Place Order and Predicted Order components*/ 
componentDidMount() {
    var state_current = this;
    socket.emit("initial_data");
    socket.on("get_data", this.getData);
    socket.on("change_data", this.changeData);
  }

/* Removing the listener before unmounting the component in order to avoid addition of multiple listener at the time revisit*/
componentWillUnmount() {
    socket.off("get_data");
    socket.off("change_data");
  }
/* When Done gets clicked, this function is called and mark_done event gets emitted which gets listened on the backend explained later on*/
markDone = id => {
    // console.log(predicted_details);
    socket.emit("mark_done", id);
  };
getFoodData() {
    return this.state.food_data.map(food => {
      return (
        <tr key={food._id}>
          <td> {food.name} </td>
          <td> {food.ordQty} </td>
          <td> {food.prodQty} </td>
          <td> {food.predQty} </td>
          <td>
            <button onClick={() => this.markDone(food._id)}>Done</button>
          </td>
        </tr>
      );
    });
  }
render() {
    return (
      <Container>
        <h2 className="h2Class">Kitchen Area</h2>
        <ReactHTMLTableToExcel
          id="test-table-xls-button"
          className="download-table-xls-button"
          table="table-to-xls"
          filename="tablexls"
          sheet="tablexls"
          buttonText="Download as XLS"
        />
<Table striped id="table-to-xls">
          <thead>
            <tr>
              <th>Name</th>
              <th>Quantity</th>
              <th>Created Till Now</th>
              <th>Predicted</th>
              <th>Status</th>
            </tr>
          </thead>
          <tbody>{this.getFoodData()}</tbody>
        </Table>
      </Container>
    );
  }
}
export default Kitchen;

Place Order Component

Path: src/main/PlaceOrder.js

import React, { Component } from "react";
import { Button, Table, Container } from "reactstrap";
import { socket } from "../global/header";
class PlaceOrder extends Component {
  constructor() {
    super();
    this.state = {
      food_data: []
      // this is where we are connecting to with sockets,
    };
  }
getData = foodItems => {
    console.log(foodItems);
    foodItems = foodItems.map(food => {
      food.order = 0;
return food;
    });
    this.setState({ food_data: foodItems });
  };
componentDidMount() {
    socket.emit("initial_data");
    var state_current = this;
    socket.on("get_data", state_current.getData);
  }
componentWillUnmount() {
    socket.off("get_data", this.getData);
  }
//Function to place the order.
sendOrder = id => {
    var order_details;
    this.state.food_data.map(food => {
      if (food._id == id) {
        order_details = food;
      }
      return food;
    });
    console.log(order_details);
    socket.emit("putOrder", order_details);
    var new_array = this.state.food_data.map(food => {
      food.order = 0;
      return food;
    });
    this.setState({ food_data: new_array });
  };
// Changing the quantity in the state which is emitted to the backend at the time of placing the order.
changeQuantity = (event, foodid) => {
    if (parseInt(event.target.value) < 0) {
      event.target.value = 0;
    }
    var new_array = this.state.food_data.map(food => {
      if (food._id == foodid) {
        food.order = parseInt(event.target.value);
      }
      return food;
    });
    this.setState({ food_data: new_array });
  };
// To get the initial data
getFoodData() {
    return this.state.food_data.map(food => {
      return (
        <tr key={food._id}>
          <td> {food.name} </td>
          <td>
            <input
              onChange={e => this.changeQuantity(e, food._id)}
              value={food.order}
              type="number"
              placeholder="Quantity"
            />
          </td>
          <td>
            <button onClick={() => this.sendOrder(food._id)}>Order</button>
          </td>
        </tr>
      );
    });
  }
render() {
    return (
      <Container>
        <h2 className="h2Class">Order Menu</h2>
        <Table striped>
          <thead>
            <tr>
              <th>Product</th>
              <th>Quantity</th>
              <th>Order</th>
            </tr>
          </thead>
          <tbody>{this.getFoodData()}</tbody>
        </Table>
      </Container>
    );
  }
}
export default PlaceOrder;

One more section called Update Predicted Path: src/main/UpdatePredicted.js similar to above section is there in the code repository.

Backend

Starting the Backend:

cd backend-my-app
npm install
node server.js

Packages used:

  1. Getting location updates for your cab on a map of a cab booking application.
  2. Getting new messages instantly on your favourite chatting application.
  3. Food order info update to the kitchen of your favourite restaurant.

3. Express: Fast, minimalist web framework for node.

Main Code

Path: backend-my-app/server.js

const express = require("express");
const http = require("http");
const socketIO = require("socket.io");
// Connection string of MongoDb database hosted on Mlab or locally
var connection_string = "**********";
// Collection name should be "FoodItems", only one collection as of now.
// Document format should be as mentioned below, at least one such document:
// {
//     "_id": {
//         "$oid": "5c0a1bdfe7179a6ca0844567"
//     },
//     "name": "Veg Roll",
//     "predQty": 100,
//     "prodQty": 295,
//     "ordQty": 1
// }
const db = require("monk")(connection_string);
const collection_foodItems = db.get("FoodItems");
// our localhost port
const port = process.env.PORT || 3000;
const app = express();
// our server instance
const server = http.createServer(app);
// This creates our socket using the instance of the server
const io = socketIO(server);
io.on("connection", socket => {
//  console.log("New client connected" + socket.id);
  //console.log(socket);
// Returning the initial data of food menu from FoodItems collection
  socket.on("initial_data", () => {
    collection_foodItems.find({}).then(docs => {
      io.sockets.emit("get_data", docs);
    });
  });
// Placing the order, gets called from /src/main/PlaceOrder.js of Frontend
  socket.on("putOrder", order => {
    collection_foodItems
      .update({ _id: order._id }, { $inc: { ordQty: order.order } })
      .then(updatedDoc => {
        // Emitting event to update the Kitchen opened across the devices with the realtime order values
        io.sockets.emit("change_data");
      });
  });
// Order completion, gets called from /src/main/Kitchen.js
  socket.on("mark_done", id => {
    collection_foodItems
      .update({ _id: id }, { $inc: { ordQty: -1, prodQty: 1 } })
      .then(updatedDoc => {
        //Updating the different Kitchen area with the current Status.
        io.sockets.emit("change_data");
      });
  });

// Functionality to change the predicted quantity value, called from /src/main/UpdatePredicted.js
  socket.on("ChangePred", predicted_data => {
    collection_foodItems
      .update(
        { _id: predicted_data._id },
        { $set: { predQty: predicted_data.predQty } }
      )
      .then(updatedDoc => {
        // Socket event to update the Predicted quantity across the Kitchen
        io.sockets.emit("change_data");
      });
  });

// disconnect is fired when a client leaves the server
  socket.on("disconnect", () => {
    console.log("user disconnected");
  });
});
/* Below mentioned steps are performed to return the Frontend build of create-react-app from build folder of backend Comment it out if running locally*/
app.use(express.static("build"));
app.use("/kitchen", express.static("build"));
app.use("/updatepredicted", express.static("build"));
server.listen(port, () => console.log(`Listening on port ${port}`));

Database: MongoDB

Mlab: Database as a service for MongoDB

Collection Name: FoodItems

Document format: At least one document is needed in the FoodItems collection with the below mentioned format.

{
"name": "Veg Roll",  // Food Name
"predQty": 100,  // Predicted Quantity
"prodQty": 295,  // Produced Quantity
"ordQty": 1   // Total Order Quantity
}

Hope you got the understanding of how to create a modular real time app using the trending MERN stack. If you found it helpful **clap **below, give starsto the project repo and share with your friends too.

Node.js, ExpressJs, MongoDB and Vue.js (MEVN Stack) Application Tutorial

Node.js, ExpressJs, MongoDB and Vue.js (MEVN Stack) Application Tutorial

In this tutorial, you'll learn how to integrate Vue.js with Node.js backend (using Express framework) and MongoDB and how to build application with Node.js, ExpressJs, MongoDB and Vue.js

In this tutorial, you'll learn how to integrate Vue.js with Node.js backend (using Express framework) and MongoDB and how to build application with Node.js, ExpressJs, MongoDB and Vue.js

Vue.js is a JavaScript framework with growing number of users. Released 4 years ago, it’s now one of the most populare front-end frameworks. There are some reasons why people like Vue.js. Using Vue.js is very simple if you are already familiar with HTML and JavaScript. They also provide clear documentation and examples, makes it easy for starters to learn the framework. Vue.js can be used for both simple and complex applications. If your application is quite complex, you can use Vuex for state management, which is officially supported. In addition, it’s also very flexible that yu can write template in HTML, JavaScript or JSX.

This tutorial shows you how to integrate Vue.js with Node.js backend (using Express framework) and MongoDB. As for example, we’re going to create a simple application for managing posts which includes list posts, create post, update post and delete post (basic CRUD functionality). I divide this tutorial into two parts. The first part is setting up the Node.js back-end and database. The other part is writing Vue.js code including how to build .vue code using Webpack.

Dependencies

There are some dependencies required for this project. Add the dependencies below to your package.json. Then run npm install to install these dependencies.

  "dependencies": {
    "body-parser": "~1.17.2",
    "dotenv": "~4.0.0",
    "express": "~4.16.3",
    "lodash": "~4.17.10",
    "mongoose": "~5.2.9",
    "morgan": "~1.9.0"
  },
  "devDependencies": {
    "axios": "~0.18.0",
    "babel-core": "~6.26.3",
    "babel-loader": "~7.1.5",
    "babel-preset-env": "~1.7.0",
    "babel-preset-stage-3": "~6.24.1",
    "bootstrap-vue": "~2.0.0-rc.11",
    "cross-env": "~5.2.0",
    "css-loader": "~1.0.0",
    "vue": "~2.5.17",
    "vue-loader": "~15.3.0",
    "vue-router": "~3.0.1",
    "vue-style-loader": "~4.1.2",
    "vue-template-compiler": "~2.5.17",
    "webpack": "~4.16.5",
    "webpack-cli": "^3.1.0"
  },

Project Structure

Below is the overview of directory structure for this project.

  app
    config
    controllers
    models
    queries
    routes
    views
  public
    dist
    src

The app directory contains all files related to server-side. The public directory contains two sub-directories: dist and src. dist is used for the output of build result, while src is for front-end code files.

Model

First, we define a model for Post using Mongoose. To make it simple, it only has two properties: title and content.

app/models/Post.js

  const mongoose = require('mongoose');

  const { Schema } = mongoose;

  const PostSchema = new Schema(
    {
      title: { type: String, trim: true, index: true, default: '' },
      content: { type: String },
    },
    {
      collection: 'posts',
      timestamps: true,
    },
  );

  module.exports = mongoose.model('Post', PostSchema);

Queries

After defining the model, we write some queries that will be needed in the controllers.

app/queries/posts.js

  const Post = require('../models/Post');

  /**
   * Save a post.
   *
   * @param {Object} post - Javascript object or Mongoose object
   * @returns {Promise.}
   */
  exports.save = (post) => {
    if (!(post instanceof Post)) {
      post = new Post(post);
    }

    return post.save();
  };

  /**
   * Get post list.
   * @param {object} [criteria] - Filter options
   * @returns {Promise.<Array.>}
   */
  exports.getPostList = (criteria = {}) => Post.find(criteria);

  /**
   * Get post by ID.
   * @param {string} id - Post ID
   * @returns {Promise.}
   */
  exports.getPostById = id => Post.findOne({ _id: id });

  /**
   * Delete a post.
   * @param {string} id - Post ID
   * @returns {Promise}
   */
  exports.deletePost = id => Post.findByIdAndRemove(id);

Controllers

We need API controllers for handling create post, get post listing, get detail of a post, update a post and delete a post.

app/controllers/api/posts/create.js

  const postQueries = require('../../../queries/posts');

  module.exports = (req, res) => postQueries.save(req.body)
    .then((post) => {
      if (!post) {
        return Promise.reject(new Error('Post not created'));
      }

      return res.status(200).send(post);
    })
    .catch((err) => {
      console.error(err);

      return res.status(500).send('Unable to create post');
    });

app/controllers/api/posts/delete.js

  const postQueries = require('../../../queries/posts');

  module.exports = (req, res) => postQueries.deletePost(req.params.id)
    .then(() => res.status(200).send())
    .catch((err) => {
      console.error(err);

      return res.status(500).send('Unable to delete post');
    });

app/controllers/api/posts/details.js

  const postQueries = require('../../../queries/posts');

  module.exports = (req, res) => postQueries.getPostById(req.params.id)
    .then((post) => {
      if (!post) {
        return Promise.reject(new Error('Post not found'));
      }

      return res.status(200).send(post);
    })
    .catch((err) => {
      console.error(err);

      return res.status(500).send('Unable to get post');
    });

app/controllers/api/posts/list.js

  const postQueries = require('../../../queries/posts');

  module.exports = (req, res) => postQueries.getPostList(req.params.id)
    .then(posts => res.status(200).send(posts))
    .catch((err) => {
      console.error(err);

      return res.status(500).send('Unable to get post list');
    });

app/controllers/api/posts/update.js

  const _ = require('lodash');

  const postQueries = require('../../../queries/posts');

  module.exports = (req, res) => postQueries.getPostById(req.params.id)
    .then(async (post) => {
      if (!post) {
        return Promise.reject(new Error('Post not found'));
      }

      const { title, content } = req.body;

      _.assign(post, {
        title, content
      });

      await postQueries.save(post);

      return res.status(200).send({
        success: true,
        data: post,
      })
    })
    .catch((err) => {
      console.error(err);

      return res.status(500).send('Unable to update post');
    });

Routes

We need to have some pages for user interaction and some API endpoints for processing HTTP requests. To make the app scalable, it’s better to separate the routes for pages and APIs.

app/routes/index.js

  const express = require('express');

  const routes = express.Router();

  routes.use('/api', require('./api'));
  routes.use('/', require('./pages'));

  module.exports = routes;


Below is the API routes.

app/routes/api/index.js

  const express = require('express');

  const router = express.Router();

  router.get('/posts/', require('../../controllers/api/posts/list'));
  router.get('/posts/:id', require('../../controllers/api/posts/details'));
  router.post('/posts/', require('../../controllers/api/posts/create'));
  router.patch('/posts/:id', require('../../controllers/api/posts/update'));
  router.delete('/posts/:id', require('../../controllers/api/posts/delete'));

  module.exports = router;


For the pages, in this tutorial, we use plain HTML file. You can easily replace it with any HTML template engine if you want. The HTML file contains a div whose id is app. Later, in Vue.js application, it will use the element with id app for rendering the content. What will be rendered on each pages is configured on Vue.js route on part 2 of this tutorial.

app/routes/pages/index.js

  const express = require('express');

  const router = express.Router();

  router.get('/posts/', (req, res) => {
    res.sendFile(`${__basedir}/views/index.html`);
  });

  router.get('/posts/create', (req, res) => {
    res.sendFile(`${__basedir}/views/index.html`);
  });

  router.get('/posts/:id', (req, res) => {
    res.sendFile(`${__basedir}/views/index.html`);
  });

  module.exports = router;

Below is the HTML file

app/views/index.html

  <!DOCTYPE html>
  <html>
    <head>
      <meta charset="utf-8">
      <title>VueJS Tutorial by Woolha.com</title>
      <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css" type="text/css" media="all" />
      <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
      <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
    </head>
    <body>
      <div id="app"></div>
      <script src="/dist/js/main.js"></script>
    </body>
  </html>

Below is the main script of the application, you need to run this for starting the server-side application.

app/index.js

  require('dotenv').config();

  const bodyParser = require('body-parser');
  const express = require('express');
  const http = require('http');
  const mongoose = require('mongoose');
  const morgan = require('morgan');
  const path = require('path');

  const dbConfig = require('./config/database');
  const routes = require('./routes');

  const app = express();
  const port = process.env.PORT || 4000;

  global.__basedir = __dirname;

  mongoose.Promise = global.Promise;

  mongoose.connect(dbConfig.url, dbConfig.options, (err) => {
    if (err) {
      console.error(err.stack || err);
    }
  });

  /* General setup */
  app.use(morgan('dev'));
  app.use(bodyParser.json());
  app.use(bodyParser.urlencoded({ extended: true }));
  app.use(morgan('dev'));

  app.use('/', routes);

  const MAX_AGE = 86400000;

  // Select which directories or files under public can be served to users
  app.use('/', express.static(path.join(__dirname, '../public'), { maxAge: MAX_AGE }));

  // Error handler
  app.use((err, req, res, next) => { // eslint-disable-line no-unused-vars
    res.status(err.status || 500);

    if (err.status === 404) {
      res.locals.page = {
        title: 'Not Found',
        noIndex: true,
      };

      console.error(`Not found: ${req.url}`);

      return res.status(404).send();
    }

    console.error(err.stack || err);

    return res.status(500).send();
  });

  http
    .createServer(app)
    .listen(port, () => {
      console.info(`HTTP server started on port ${port}`);
    })
    .on('error', (err) => {
      console.error(err.stack || err);
    });

  process.on('uncaughtException', (err) => {
    if (err.name === 'MongoError') {
      mongoose.connection.emit('error', err);
    } else {
      console.error(err.stack || err);
    }
  });

  module.exports = app;

That’s all for the server side preparation. On the next part, we’re going to set up the Vue.js client-side application and build the code into a single JavaScript file ready to be loaded from HTML.

Then, we build the code using Webpack, so that it can be loaded from HTML. In this tutorial, we’re building a simple application with basic CRUD functionality for managing posts.

Create Vue.js Components

For managing posts, there are three components we’re going to create. The first one is for creating a new post. The second is for editing a post. The other is for managing posts (displaying list of posts and allow post deletion)

First, this is the component for creating a new post. It has one method createPost which validate data and send HTTP request to the server. We use axios for sending HTTP request.

public/src/components/Posts/Create.vue

  <template>
    <b-container>
      <h1 class="d-flex justify-content-center">Create a Post</h1>
      <p v-if="errors.length">
        <b>Please correct the following error(s):</b>
        <ul>
          <li v-for="error in errors">{{ error }}</li>
        </ul>
      </p>
      <b-form @submit.prevent>
        <b-form-group>
          <b-form-input type="text" class="form-control" placeholder="Title of the post" v-model="post.title"></b-form-input>
        </b-form-group>
        <b-form-group>
          <b-form-textarea class="form-control" placeholder="Write the content here" v-model="post.content"></b-form-textarea>
        </b-form-group>
        <b-button variant="primary" v-on:click="createPost">Create Post</b-button>
      </b-form>
    </b-container>
  </template>

  <script>
    import axios from 'axios';

    export default {
      data: () => ({
        errors: [],
        post: {
          title: '',
          content: '',
        },
      }),
      methods: {
        createPost(event) {
          if (event) {
            event.preventDefault();
          }

          if (!this.post.title) {
            this.errors = [];

            if (!this.post.title) {
              this.errors.push('Title required.');
            }

            return;
          }

          const url = 'http://localhost:4000/api/posts';
          const param = this.post;

          axios
            .post(url, param)
            .then((response) => {
              console.log(response);
              window.location.href = 'http://localhost:4000/posts';
            }).catch((error) => {
              console.log(error);
            });
        },
      }
    }
  </script>


Below is the component for editing a post. Of course, we need the current data of the post before editing it. Therefore, there’s fetchPost method called when the component is created. There’s also updatePost method which validate data and call the API for updating post.

public/src/components/Posts/Edit.vue

  <template>
    <b-container>
      <h1 class="d-flex justify-content-center">Edit a Post</h1>
      <p v-if="errors.length">
        <b>Please correct the following error(s):</b>
        <ul>
          <li v-for="error in errors">{{ error }}</li>
        </ul>
      </p>
      <b-form @submit.prevent>
        <b-form-group>
          <b-form-input type="text" class="form-control" placeholder="Title of the post" v-model="post.title"></b-form-input>
        </b-form-group>
        <b-form-group>
          <b-form-textarea class="form-control" placeholder="Write the content here" v-model="post.content"></b-form-textarea>
        </b-form-group>
        <b-button variant="primary" v-on:click="updatePost">Update Post</b-button>
      </b-form>
    </b-container>
  </template>

  <script>
    import axios from 'axios';

    export default {
      data: () => ({
        errors: [],
        post: {
          _id: '',
          title: '',
          content: '',
        },
      }),
      created: function() {
        this.fetchPost();
      },
      methods: {
        fetchPost() {
          const postId = this.$route.params.id;
          const url = `http://localhost:4000/api/posts/${postId}`;

          axios
            .get(url)
            .then((response) => {
              this.post = response.data;
              console.log('this.post;');
              console.log(this.post);
          });
        },
        updatePost(event) {
          if (event) {
            event.preventDefault();
          }

          if (!this.post.title) {
            this.errors = [];

            if (!this.post.title) {
              this.errors.push('Title required.');
            }

            return;
          }

          const url = `http://localhost:4000/api/posts/${this.post._id}`;
          const param = this.post;

          axios
            .patch(url, param)
            .then((response) => {
                console.log(response);
              window.alert('Post successfully saved');
            }).catch((error) => {
              console.log(error);
            });
        },
      }
    }
  </script>


For managing posts, we need to fetch the list of post first. Similar to the edit component, in this component, we have fetchPosts method called when the component is created. For deleting a post, there’s also a method deletePost. If post successfully deleted, the fetchPosts method is called again to refresh the post list.

public/src/components/Posts/List.vue

  <template>
    <b-container>
      <h1 class="d-flex justify-content-center">Post List</h1>
      <b-button variant="primary" style="color: #ffffff; margin: 20px;"><a href="/posts/create" style="color: #ffffff;">Create New Post</a></b-button>
      <b-container-fluid v-if="posts.length">
        <table class="table">
          <thead>
            <tr class="d-flex">
              <td class="col-8">Titleqqqqqqqqq</td>
              <td class="col-4">Actions</td>
            </tr>
          </thead>
          <tbody>
            <tr v-for="post in posts" class="d-flex">
              <td class="col-8">{{ post.title }}</td>
              <td class="col-2"><a v-bind:href="'http://localhost:4000/posts/' + post._id"><button type="button" class="btn btn-primary"><i class="fa fa-edit" aria-hidden="true"></i></button></a></td>
              <td class="col-2"><button type="button" class="btn btn-danger" v-on:click="deletePost(post._id)"><i class="fa fa-remove" aria-hidden="true"></i></button></td>
            </tr>
          </tbody>
        </table>
      </b-container-fluid>
    </b-container>
  </template>

  <script>
    import axios from 'axios';

    export default {
      data: () => ({
        posts: [],
      }),
      created: () => {
        this.fetchPosts();
      },
      methods: {
        fetchPosts() {
          const url = 'http://localhost:4000/api/posts/';

          axios
            .get(url)
            .then((response) => {
              console.log(response.data);
              this.posts = response.data;
          });
        },
        deletePost(id) {
          if (event) {
            event.preventDefault();
          }

          const url = `http://localhost:4000/api/posts/${id}`;
          const param = this.post;

          axios
            .delete(url, param)
            .then((response) => {
              console.log(response);
              console.log('Post successfully deleted');

              this.fetchPosts();
            }).catch((error) => {
              console.log(error);
            });
        },
      }
    }
  </script>


All of the components above are wrapped into a root component which roles as the basic template. The root component renders the navbar which is same across all components. The component for each routes will be rendered on router-view.

public/src/App.vue

  <template>
    <div>
      <b-navbar toggleable="md" type="dark" variant="dark">
        <b-navbar-toggle target="nav_collapse"></b-navbar-toggle>
        <b-navbar-brand to="/">My Vue App</b-navbar-brand>
        <b-collapse is-nav id="nav_collapse">
          <b-navbar-nav>
            <b-nav-item to="/">Home</b-nav-item>
            <b-nav-item to="/posts">Manage Posts</b-nav-item>
          </b-navbar-nav>
        </b-collapse>
      </b-navbar>
      <!-- routes will be rendered here -->
      <router-view />
    </div>
  </template>

  <script>

  export default {
    name: 'app',
    data () {},
    methods: {}
  }
  </script>


For determining which component should be rendered, we use Vue.js’ router. For each routes, we need to define the path, component name and the component itself. A component will be rendered if the current URL matches the path.

public/src/router/index.js

  import Vue from 'vue'
  import Router from 'vue-router'

  import CreatePost from '../components/Posts/Create.vue';
  import EditPost from '../components/Posts/Edit.vue';
  import ListPost from '../components/Posts/List.vue';

  Vue.use(Router);

  let router = new Router({
    mode: 'history',
    routes: [
      {
        path: '/posts',
        name: 'ListPost',
        component: ListPost,
      },
      {
        path: '/posts/create',
        name: 'CreatePost',
        component: CreatePost,
      },
      {
        path: '/posts/:id',
        name: 'EditPost',
        component: EditPost,
      },
    ]
  });

  export default router;


Lastly, we need a main script as the entry point which imports the main App component and the router. Inside, it creates a new Vue instance

webpack.config.js

  import BootstrapVue from 'bootstrap-vue';
  import Vue from 'vue';

  import App from './App.vue';
  import router from './router';

  Vue.use(BootstrapVue);
  Vue.config.productionTip = false;
  new Vue({
    el: '#app',
    router,
    render: h => h(App),
  });

Configure Webpack

For building the code into a single JavaSript file. Below is the basic configuration for Webpack 4.

webpack.config.js

  const { VueLoaderPlugin } = require('vue-loader');

  module.exports = {
    entry: './public/src/main.js',
    output: {
      path: `${__dirname}/public/dist/js/`,
      filename: '[name].js',
    },
    resolve: {
      modules: [
        'node_modules',
      ],
      alias: {
        // vue: './vue.js'
      }
    },
    module: {
      rules: [
        {
          test: /\.css$/,
          use: [
            'vue-style-loader',
            'css-loader'
          ]
        },
        {
          test: /\.vue$/,
          loader: 'vue-loader',
          options: {
            loaders: {
            }
            // other vue-loader options go here
          }
        },
        {
          test: /\.js$/,
          loader: 'babel-loader',
          exclude: /node_modules/
        },
      ]
    },
    plugins: [
      new VueLoaderPlugin(),
    ]

After that, run ./node_modules/webpack/bin/webpack.js. You can add the command to the scripts section of package.json, so you can run Webpack with a shorter command npm run build, as examplified below.

  "dependencies": {
    ...
  },
  "devDependencies": {
    ...
  },
  "scripts": {
    "build": "./node_modules/webpack/bin/webpack.js",
    "start": "node app/index.js"
  },

Finally, you can start to try the application. This code is also available on Woolha.com’s Github.

How to create Restful CRUD API with Node.js MongoDB and Express.js

How to create Restful CRUD API with Node.js MongoDB and Express.js

How to create Restful CRUD API with Node.js MongoDB and Express.js

In this blog, we are going to learn how to perform CRUD (Create, Read, Update and Delete) operations with the help of Rest API using Node.js, MongoDB as our database, and Expess.

REST

In simple terms, REST stands for Representational State Transfer. It is an architectural style design for distributed hypermedia, or an Application Programming Interface (API). In REST, we use various standard HTTP methods like GETPOSTPUT and DELETE to perform any CRUD operation on resource.

Resource

In REST, everything is Resource. A resource can be an image, document, a temporary service, a collection of other resource, and any other object. Each resource has resource identifier to identify it.

HTTP Methods for CRUD

As per REST guidelines, we should use only HTTP methods to perform CRUD operation on any resource. In this blog, we are going to use 4 HTTP methods like GETPOSTPUT and DELETE to make our REST API.

Let’s have a brief introduction of each http method here.

  • HTTP GET

The HTTP GET is used to Read or Retrieve any resource. It returns the XML or JSON data with HTTP status code of 200. GET method is considered as safe, because we are just getting or reading the resource data, not doing any changes in the resource data.

  • HTTP GET

The HTTP POST is used to Create a new resource. On successful creation of resource, it will return HTTP status code of 201, a Location header with a link to the newly created resource.

  • HTTP GET

The HTTP PUT is used to Update any existing resource. On successful, it will return HTTP status code of 200.

  • HTTP GET

The HTTP DELETE, as the name suggests, is used to Delete any existing resource. On successful, it will return HTTP status code of 200.

Let’s move forward into the details of other pieces of creating our REST API.

Express.js

We are going to use Express.js or simply Express. It is a web application framework for Node.js. It has been released as free and open source software. You can create web application and APIs using Express. It has support for routing, middleware, view system etc.

Mongoose

Mongoose is Object Document Mapping or ODM tool for Node.js and MongoDB. Mongoose provide a straight-forward, schema based solution to model to your application data. It includes built-in type casting, validation, query building, business logic hooks and many more.

Prerequisites

You must have Node.js and MongoDB installed on your machine. Click the below links, if you don’t have any one of them.

Install Node.js

Install MongoDB

For MongoDB, I am using mLab free account for online MongoDB database. You can try this one as well, instead of installing on your local machine.
Database-as-a-Service for MongoDB

Install Postman – Google Chrome for testing purpose.

After setting up prerequisites, let move forward to build our application.

Application Introduction

In our application, we are going to create a product based application. We will use REST APIs to create, update, get and delete the product. Let’s go to create our application.

1. Create package.json

Let’s create a folder, and start with creating package.json file first. Use this command in your terminal window.

For MongoDB, I am using mLab free account for online MongoDB database. You can try this one as well, instead of installing on your local machine.#### package.json

{
  "name": "product-app",
  "version": "1.0.0",
  "description": "This is zepbook product app",
  "main": "server.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "ZeptoBook",
  "license": "MIT"
}

If you notice line 5, we have defined server.js as our main entry point.

2. Install Packages

Let’s install all the expressmongoose and body-parser package dependencies in our app.

For MongoDB, I am using mLab free account for online MongoDB database. You can try this one as well, instead of installing on your local machine.
Once these packages installed successfully, our package.json file will be updated automatically. Our latest file will be like this.

package.json

{
  "name": "product-app",
  "version": "1.0.0",
  "description": "This is zepbook product app",
  "main": "server.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "ZeptoBook",
  "license": "MIT",
  "dependencies": {
    "body-parser": "^1.18.3",
    "express": "^4.16.4",
    "mongoose": "^5.4.2"
  }
}

Notice dependencies section of our file, all these packages are mentioned there.

3. Creating Our Server

Let’s create a server.js file in the root directory of the application.

server.js

// get dependencies
const express = require('express');
const bodyParser = require('body-parser');

const app = express();

// parse requests
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())

// default route
app.get('/', (req, res) => {
    res.json({"message": "Welcome to ZeptoBook Product app"});
});

// listen on port 3000
app.listen(3000, () => {
    console.log("Server is listening on port 3000");
});

Let’s briefly review our above code. First of all, we imported the required dependencies in our server.js file.

body-parser

It is Node.js body parser middleware. It parse the incoming request bodies in a middleware before your handlers, available under the req.body property.

Learn more about bodyParser.urlencoded([options])

Learn more about bodyParser.json([options])

Then, we define a default route using GET Http method. By default, it will return our message on default url.

Finally, we are going to listen all incoming requests on port 3000.

4. Run the server

Once everything is all set, let’s wake up our server by running this command in our terminal window.

For MongoDB, I am using mLab free account for online MongoDB database. You can try this one as well, instead of installing on your local machine.> For MongoDB, I am using mLab free account for online MongoDB database. You can try this one as well, instead of installing on your local machine.####

5. Create Configuration file

Let’s create a config file in our app, where we can define various constants like dbconnection or port number instead of hard-coded it everywhere. So, create a config.js file in your app folder.

config.js

module.exports = {    url: 'mongodb://<dbUserName>:<dbUserPassword>@ds251002.mlab.com:51002/adeshtestdb',    
serverport: 3000 }

So, here I mentioned two constants in config.js file.

  • HTTP GET

6. Connecting to Database

Let’s connect with our MongoDb database. Add these lines of codes in server.js file after the app.use(bodyParser.json());

// Configuring the database
const config = require('./config.js');
const mongoose = require('mongoose');

mongoose.Promise = global.Promise;

// Connecting to the database
mongoose.connect(config.url, {
    useNewUrlParser: true
}).then(() => {
    console.log("Successfully connected to the database");    
}).catch(err => {
    console.log('Could not connect to the database. Exiting now...', err);
    process.exit();
});

Also, we are going to replace our hard-coded server port with our config constant in server.js file

// listen on port 3000
app.listen(config.serverport, () => {
    console.log("Server is listening on port 3000");
});

Here, you can see, we are now using config.serverport in app.listen().

Now, run again the server using this command.

For MongoDB, I am using mLab free account for online MongoDB database. You can try this one as well, instead of installing on your local machine.> For MongoDB, I am using mLab free account for online MongoDB database. You can try this one as well, instead of installing on your local machine.> For MongoDB, I am using mLab free account for online MongoDB database. You can try this one as well, instead of installing on your local machine.####

7. Creating Product Model

Let’s create a product model in our app folder in order to save the data in our db. Create a product.model.js file in your app.

product.model.js

const mongoose = require('mongoose');

const ProductSchema = mongoose.Schema({
    title: String,
    description: String,
    price: Number,
    company: String
}, {
    timestamps: true
});

module.exports = mongoose.model('Products', ProductSchema);

Here, we have defined our ProductSchema with following properties. Along with this, we also set timestampsproperty to true. This property will add two fields automatically to schema. These fields are : createdAt and updatedAt in your schema.

8. Creating our Controller’s functions

We are going to write all functions related to create, retrieve, update and delete products in our controller file. Let’s create a controller file named product.controller.js in your app folder.

product.controller.js

const Product = require('./product.model.js');

//Create new Product
exports.create = (req, res) => {
    // Request validation
    if(!req.body) {
        return res.status(400).send({
            message: "Product content can not be empty"
        });
    }

    // Create a Product
    const product = new Product({
        title: req.body.title || "No product title", 
        description: req.body.description,
        price: req.body.price,
        company: req.body.company
    });

    // Save Product in the database
    product.save()
    .then(data => {
        res.send(data);
    }).catch(err => {
        res.status(500).send({
            message: err.message || "Something wrong while creating the product."
        });
    });
};

// Retrieve all products from the database.
exports.findAll = (req, res) => {
    Product.find()
    .then(products => {
        res.send(products);
    }).catch(err => {
        res.status(500).send({
            message: err.message || "Something wrong while retrieving products."
        });
    });
};

// Find a single product with a productId
exports.findOne = (req, res) => {
    Product.findById(req.params.productId)
    .then(product => {
        if(!product) {
            return res.status(404).send({
                message: "Product not found with id " + req.params.productId
            });            
        }
        res.send(product);
    }).catch(err => {
        if(err.kind === 'ObjectId') {
            return res.status(404).send({
                message: "Product not found with id " + req.params.productId
            });                
        }
        return res.status(500).send({
            message: "Something wrong retrieving product with id " + req.params.productId
        });
    });
};

// Update a product
exports.update = (req, res) => {
    // Validate Request
    if(!req.body) {
        return res.status(400).send({
            message: "Product content can not be empty"
        });
    }

    // Find and update product with the request body
    Product.findByIdAndUpdate(req.params.productId, {
        title: req.body.title || "No product title", 
        description: req.body.description,
        price: req.body.price,
        company: req.body.company
    }, {new: true})
    .then(product => {
        if(!product) {
            return res.status(404).send({
                message: "Product not found with id " + req.params.productId
            });
        }
        res.send(product);
    }).catch(err => {
        if(err.kind === 'ObjectId') {
            return res.status(404).send({
                message: "Product not found with id " + req.params.productId
            });                
        }
        return res.status(500).send({
            message: "Something wrong updating note with id " + req.params.productId
        });
    });
};

// Delete a note with the specified noteId in the request
exports.delete = (req, res) => {
    Product.findByIdAndRemove(req.params.productId)
    .then(product => {
        if(!product) {
            return res.status(404).send({
                message: "Product not found with id " + req.params.productId
            });
        }
        res.send({message: "Product deleted successfully!"});
    }).catch(err => {
        if(err.kind === 'ObjectId' || err.name === 'NotFound') {
            return res.status(404).send({
                message: "Product not found with id " + req.params.productId
            });                
        }
        return res.status(500).send({
            message: "Could not delete product with id " + req.params.productId
        });
    });
};

9. Defining Product API’s Routes

Next step is to create our api routes. Create a product.routes.js file in your app folder.

product.routes.js

module.exports = (app) => {
    const products = require('./product.controller.js');

    // Create a new Product
    app.post('/products', products.create);

    // Retrieve all Products
    app.get('/products', products.findAll);

    // Retrieve a single Product with productId
    app.get('/products/:productId', products.findOne);

    // Update a Note with productId
    app.put('/products/:productId', products.update);

    // Delete a Note with productId
    app.delete('/products/:productId', products.delete);
}

Note: import this route file in our server.js file after these lines. See line 4 in the below code.

// Configuring the database
const config = require('./config.js');
const mongoose = require('mongoose');
require('./product.routes.js')(app);  //Add route file here

10. Enable the CORS

If you try to access your api routes through your client-side app, you might face Access-Control-Allow-Originerror messages. So, in order to avoid these message, we are also enabling CORS in our server.js file.

server.js

//Enable CORS for all HTTP methods
app.use(function(req, res, next) {
    res.header("Access-Control-Allow-Origin", "*");
    res.header("Access-Control-Allow-Methods", "GET, PUT, POST, DELETE, OPTIONS");
    res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
    next();
  });

So, this will be our final server.js file.

server.js

// get dependencies
const express = require('express');
const bodyParser = require('body-parser');

const app = express();

// parse requests
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());

//Enable CORS for all HTTP methods
app.use(function(req, res, next) {
    res.header("Access-Control-Allow-Origin", "*");
    res.header("Access-Control-Allow-Methods", "GET, PUT, POST, DELETE, OPTIONS");
    res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
    next();
  });

// Configuring the database
const config = require('./config.js');
const mongoose = require('mongoose');
require('./product.routes.js')(app);

mongoose.Promise = global.Promise;

// Connecting to the database
mongoose.connect(config.url, {
    useNewUrlParser: true
}).then(() => {
    console.log("Successfully connected to the database");    
}).catch(err => {
    console.log('Could not connect to the database. Exiting now...', err);
    process.exit();
});

// default route
app.get('/', (req, res) => {
    res.json({"message": "Welcome to ZeptoBook Product app"});
});

// listen on port 3000
app.listen(config.serverport, () => {
    console.log("Server is listening on port 3000");
});

This will be our project structure.

                               ![](https://i2.wp.com/www.zeptobook.com/wp-content/uploads/2019/01/Product-App-folder.png?resize=495%2C395&ssl=1)

Testing our REST APIs

Now, it’s time to test our all REST APIs for CRUD Operation.

  • HTTP GET

I have added few products in the database. See the below screen shot.

                              ![](https://i0.wp.com/www.zeptobook.com/wp-content/uploads/2019/01/List-of-Products.png?fit=519%2C1024&ssl=1) 
  • HTTP GET

  • HTTP GET

  • HTTP GET

Before Update

After Update

  • HTTP GET