By shahid

RethinkDB and Nodejs is perfect combination to build your next real time web application. RethinkDB provides combination of SQL and NoSQL data model and live feed which is perfect for building real time web application.

What we’ll be building

In this guide we will develop basic polling app using Node and RethinkDB. It require almost no prior knowledge of NodeJS but having basic information is plus point.

We’ll be creating a simple polling application which will let user to

  • Create new poll with 3 choices.
  • Allow user to vote.
  • Show the vote count updates live.

At the end of this tutorial we’ll have something like this.

Node and rethinkDB

DEMO DOWNLOAD

We’ll be using RethinkDB as our database engine and Nodejs as application server. We’ll be using Angular and Angular-material library for front-end design.

Introduction

RethinkDB and Nodejs together is best pair to develop real time web application. In this tutorial we will develop polling application where user can create polls, vote them and see the result of voting live. We can do this using Socket.io and any database such as MySQL or MongoDB but difference with them and RethinkDB is “Pull” and “Push” of changes.

In case of MySQL or MongoDB we need to perform polling in order to find out the changes in our database. In case of RethinKDB, it will push the changes to middle tier and all you need to do from there is broadcast it to client.

Let’s begin with developing our polling app using RethinkDB and Nodejs.

Installation

I am sure you have Node.js installed in your computer. If not please visit node.js official website and download the installer suitable to your operating system.

Note : Make sure you have latest Nodejs installed in your system because we are going to use ES6 features to code this app.

I have covered installation of RethinKDB Server in this tutorial in detail. Click on the link and install the RethinkDB in your computer.

Project Setup

The first goal is to develop our Nodejs Server and interface RethinkDB with it.To maintain the code structure as neat as possible we will follow this folder structure.

| - config [ Contains project wide configuration files ]
| - controller [ Contains the routes of the express ]
| - models [ Contains the database interaction code ]
| - test [ contains test files]
| - view [ Contains front end files ]
- app.js - Main server file.
- package.json

Create a folder and generate a package.json in it. Package.json is a project manifest file that describes our project.

Tip : Best practice is to use npm init command to generate package.json for fresh new project.

Here is sample package.json file.

{
  "name": "polling-app-node-rethinkdb",
  "version": "1.0.0",
  "description": "Building real time polling application using RethinkDB and Node.js.",
  "main": "app.js"
}

Let’s install require modules for our project. We will use Express as web framework, rethinkdb official client driver and socket.io to handle the real time communication with front end. Run following command to install them and it will write package entries in package.json file.

Related learning :

npm install --save express rethinkdb socket.io async body-parser

You may need to provide sudo access depending upon the location and access rights to the folder.

The Nodejs Server

We need a web server to deliver the static files and to handle the RethinkDB operation. Here is sample Nodejs Server written using Express.

var express = require('express');
var bodyParser = require('body-parser');
var app = express();
var http = require('http').Server(app);
var router = express.Router();

router.get('/', function(req, res){
  res.send('<h1>Hello world</h1>');
});

app.use(bodyParser.json());
app.use('/',router);

http.listen(3000, function(){
  console.log('listening on port 3000');
});

If you run node app.js, you should see following.

Node and RethinkDB

And if you point your browser to http://localhost:3000
Node and RethinkDB app

Configuring routes

Let’s define various HTTP routes for our application. We need following HTTP routes to handle the working of the application.

  • GET /polls – Should return all polls from RethinkDB.
  • POST /polls – Should create new poll.

We need to define this in our controllers folder. Create index.js in controllers folder and add following code.

/controllers/index.js
var express = require('express');
var router = express.Router();

/**
  * @description
  * First route will handle the static html file delievery.
  * Second route will handle the API calls.
*/

router.use('/',require('./home'));
router.use('/polls',require('./polls'));

module.exports = router;

In this file we have defined two routes. First is ‘/’ where we will place the code to deliver the static files. All those code will be placed in home.js file. In second route we will define the API calls for the polls operation.

Here is home.js placed inside the controllers folder.

/controllers/home.js
var express = require('express');
var router = express.Router();

router.get('/',function(req,res) {
  res.send('<h1>Hello World</h2>');
});

module.exports = router;

Here is skeleton code of polls.js.

/controllers/polls.js
var express = require('express');
var router = express.Router();

router.route('/')
  .get(function(req,res) {
    // Code to fetch the polls.
  })
  .post(function(req,res) {
    // Code to add new polls.
  })
  .put(function(req,res) {
    // Code to update votes of poll.
  });

module.exports = router;

We need to modify our app.js to use this file as the route. Here is modified app.js.

app.js
var express = require('express');
var bodyParser = require('body-parser');
var app = express();
var http = require('http').Server(app);

/**
  Adding the controllers.
*/

app.use(bodyParser.json());
app.use(require('./controllers'));

http.listen(3000, function(){
  console.log('listening on port 3000');
});

Run the code using node app.js and view the app in browser.

Integrating RethinkDB

Let’s integrate RethinkDB in our application. We have already installed the required module (rethinkdb) and we’ll write some functions in order to do the CRUD operation. Create new file in models folder named db.js which will contain code to connect to RethinkDB and connect to RethinkDB database.

/models/db.js
"use strict";
var rethinkdb = require('rethinkdb');
var async = require('async');

class db {
  setupDb() {
    var self = this;
    async.waterfall([
      function(callback) {
        self.connectToRethinkDbServer(function(err,connection) {
          if(err) {
            return callback(true,"Error in connecting RethinkDB");
          }
          callback(null,connection);
        });
      },
      function(connection,callback) {
        rethinkdb.dbCreate('polls').run(connection,function(err, result) {
          if(err) {
            console.log("Database already created");
          } else {
            console.log("Created new database");
          }
          callback(null,connection);
        });
      },
      function(connection,callback) {
        rethinkdb.db('polls').tableCreate('poll').run(connection,function(err,result) {
          connection.close();
          if(err) {
            console.log("table already created");
          } else {
            console.log("Created new table");
          }
          callback(null,"Database is setup successfully");
        });
      }
    ],function(err,data) {
      console.log(data);
    });
  }

  connectToRethinkDbServer(callback) {
    rethinkdb.connect({
      host : 'localhost',
      port : 28015
    }, function(err,connection) {
      callback(err,connection);
    });
  }

  connectToDb(callback) {
    rethinkdb.connect({
      host : 'localhost',
      port : 28015,
      db : 'polls'
    }, function(err,connection) {
      callback(err,connection);
    });
  }
}

module.exports = db;

We have defined three functions :

  • setupDb()
  • connectToRethinkDb()
  • connectToDb()

setupDb() contains three inner function which executes one after another by using Async module. This is good way to avoid callback hell. In first function we are connecting to RethinkDB Server, in second function we are creating database and it will return error if it is already created, in third function we are creating table if not already created.

We will call this function just once during the initializing of the main application.

Then we have defined connectToRethinkDbServer() and connectToDb() function which performs connection to RethinkDB Server and RethinkDB database respectively.

We need to modify our app.js file again to incorporate the database setup code.

app.js
var express = require('express');
var bodyParser = require('body-parser');
var app = express();
var http = require('http').Server(app);
var db = require('./models/db');

/**
  Adding the controllers.
*/

var dbModel = new db();
/**
  Setting up the database and creating table.
*/

dbModel.setupDb();
app.use(bodyParser.json());
app.use(require('./controllers'));

http.listen(3000, function(){
  console.log('listening on port 3000');
});

If you now reload the Server, you should see following in the console.
Node and RethinkDB interfacing

Writing RethinkDB functions

We have to perform following operation in RethinkDB database.

  • Creating new poll.
  • Updating votes.
  • Getting all polls.

We’ll insert the data in our RethinkDB schema in following manner.

{
  "Question" : "Which is best phone?",
  "polls" : [
    {
      "option" : "iPhone", vote : 0
    },
    {
      "option" : "Android", vote : 0
    }
   ]
}

Let’s write our code to do this operation. Let’s create another file in our models folder named polls.js and add code to do task listed above.

Here is skeleton of the polls.js

/models/polls.js
"use strict";
var rethinkdb = require('rethinkdb');
var db = require('./db');
var async = require('async');

class polls {

}

module.exports = polls;

First we a function to add new poll. Here it is.

/models/polls.js
"use strict";
var rethinkdb = require('rethinkdb');
var db = require('./db');
var async = require('async');

class polls {
  addNewPolls(pollData,callback) {
    async.waterfall([
      function(callback) {
        db.connectToDb(function(err,connection) {
          if(err) {
            return callback(true,"Error connecting to database");
          }
          callback(null,connection);
        });
      },
      function(connection,callback) {
        rethinkdb.table('poll').insert({
            "question" : pollData.question,
            "polls" : pollData.polls
        }).run(connection,function(err,result) {
          connection.close();
          if(err) {
            return callback(true,"Error happens while adding new polls");
          }
          callback(null,result);
        });
      }
    ],function(err,data) {
      callback(err === null ? false : true,data);
    });
  }
}

module.exports = polls;

Again we are using Async to avoid callback nesting as much as possible. Here we are first connecting to our database by using the function written in db.js and then adding the poll data in our table using insert command of RethinkDB. We will pass values in pollData variable in same format we mentioned earlier.

Next we need a function to update the vote. In order to perform this operation we require the system generated id of the object and the option of which vote we need to update.

/models/polls.js
"use strict";
var rethinkdb = require('rethinkdb');
var db = require('./db');
var async = require('async');

class polls {
  addNewPolls(pollData,callback) {
    ......................................
  }

  votePollOption(pollData,callback) {
    async.waterfall([
      function(callback) {
        db.connectToDb(function(err,connection) {
          if(err) {
            return callback(true,"Error connecting to database");
          }
          callback(null,connection);
        });
      },
      function(connection,callback) {
        rethinkdb.table('poll').get(pollData.id).run(connection,function(err,result) {
          if(err) {
            return callback(true,"Error fetching polls to database");
          }
          for(var pollCounter = 0; pollCounter < result.polls.length; pollCounter++) {
            if(result.polls[pollCounter].option === pollData.option) {
              result.polls[pollCounter].vote += 1;
              break;
            }
          }
          rethinkdb.table('polls').get(pollData.id).update(result).run(connection,function(err,result) {
            connection.close();
            if(err) {
              return callback(true,"Error updating the vote");
            }
            callback(null,result);
          });
        });
      }
    ],function(err,data) {
      callback(err === null ? false : true,data);
    });
  }
}

module.exports = polls;

We have created a function where we are first connecting to our database, then we are verifying the poll data by using get method and in that we are passing the system generated id. If we found an result, we are looping over the polls keys which will be an array of object and contains the options and vote. And if the requested option and option in object matches, we increment the vote by 1.

And once the increment is performed we are simply updating the object by using get and update method.

Next we need a function to return all of the polls in our table. Here is a code for it.

/models/polls.js
"use strict";
var rethinkdb = require('rethinkdb');
var db = require('./db');
var async = require('async');

class polls {
  addNewPolls(pollData,callback) {
   .............................
  }

  votePollOption(pollData,callback) {
   .............................
  }

  getAllPolls(callback) {
    async.waterfall([
      function(callback) {
        db.connectToDb(function(err,connection) {
          if(err) {
            return callback(true,"Error connecting to database");
          }
          callback(null,connection);
        });
      },
      function(connection,callback) {
        rethinkdb.table('poll').run(connection,function(err,cursor) {
          connection.close();
          if(err) {
            return callback(true,"Error fetching polls to database");
          }
          cursor.toArray(function(err, result) {
            if(err) {
              return callback(true,"Error reading cursor");
            }
            callback(null,result);
          });
        });
      }
    ],function(err,data) {
      callback(err === null ? false : true,data);
    });
  }
}

module.exports = polls;

We are again connecting our database first, then selecting all of the data from the table. Once we get the data, we are converting the cursor to an Array and sending it back to caller function.

Now we need to integrate our controller with model. All we need is to modify our polls.js and call respective model functions from the routes. Here is our polls.js after modification.

/controllers/polls.js
var express = require('express');
var router = express.Router();
// require model file.
var pollModel = require('../models/polls');

router.route('/')
  .get(function(req,res) {
    // Code to fetch the polls.
    var pollObject = new pollModel();
    // Calling our model function.
    pollObject.getAllPolls(function(err,pollResponse) {
      if(err) {
        return res.json({"responseCode" : 1, "responseDesc" : pollResponse});
      }
      res.json({"responseCode" : 0, "responseDesc" : "Success", "data" : pollResponse});
    });
  })
  .post(function(req,res) {
    // Code to add new polls.
    var pollObject = new pollModel();
    // Calling our model function.
    // We nee to validate our payload here.
    pollObject.addNewPolls(req.body,function(err,pollResponse) {
      if(err) {
        return res.json({"responseCode" : 1, "responseDesc" : pollResponse});
      }
      res.json({"responseCode" : 0, "responseDesc" : "Success","data" : pollResponse});
    });
  })
  .put(function(req,res) {
    // Code to update votes of poll.
    var pollObject = new pollModel();
    // Calling our model function.
    // We need to validate our payload here.
    pollObject.votePollOption(req.body,function(err,pollResponse) {
      if(err) {
        return res.json({"responseCode" : 1, "responseDesc" : pollResponse});
      }
      res.json({"responseCode" : 0, "responseDesc" : "Success", "data" : pollResponse});
    });
  });

module.exports = router;

Before moving ahead let’s test our code and see are we able to create/vote/get polls or not. Before starting Node Server make sure RethinkDB Server is up and running. Run RethinkDB Server from terminal using following command.

rethinkdb

Run Node Server using following command.

node app.js

Open any API simulator application, we recommend POSTMAN chrome extension. We need to validate following three cases.

  • Create new poll – POST /polls
  • Update vote – PUT /polls
  • Get all votes – GET /polls

Create new poll – POST /polls

Hit POST request to http://localhost:3000/polls with following data.

{
    "question" : "Best phone ? ",
    "polls" : [
        {
            "option" : "Android", "vote" : 0
        }
    ]
}

Node and RethinkDB

Update vote – PUT /polls

To perform this we need to have id of the poll. Copy the one from response. Hit PUT request to http://localhost:3000/polls with following data.

{
    "id" : "f4eba54d-644c-4c27-b8a9-846fded0cef7",
    "option" : "Android"
}

Node and RethinkDB

Get all votes – GET /polls

Just hit the GET request to http://localhost:3000/polls
Node and RethinkDB

It seems we have our API’s ready an working. Its time to design and code User Interface and bind these API to have a fully fledged application.

Developing front-end

In front-end, we will use following libraries.

  • Angular
  • Angular material

Material user interface is Google’s innovation in user interface technology. Angular material is very rich library for Angular to develop application which gives look and feel of material design.

To know more about Material design, visit here.

Let’s begin with installation of tools. I prefer Bower as a front-end package manager to install libraries on go. However it’s not mandatory, you can always install it manually.

We need bower installed globally and project wise as well. To install it globally to use it as command line tool;run following command.

npm install bower -g

You may need sudo access for this.

Once installation is complete, switch to project directory and install bower as developer dependency.

npm install bower --save-dev

After installation, switch to view folder and install libraries required.

bower install angular-material -D && bower install angular-route && bower install angular-messages

This will install libraries we need to develop front-end of the application. You may be able to bower_components folder in view directory. We are good to go.

Designing home page

Home page will contain menu bar with three options and down below the menu we will have listed polls. Here is code for same.

/view/index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Polling app using Node and RethinkDB</title>
    <link href="./bower_components/angular-material/angular-material.css" rel="stylesheet" />
  </head>
  <body ng-app='starterApp' layout='column' ng-controller='pollingController'>
    <script src="./bower_components/angular/angular.js" type="text/javascript" ></script>
    <script src="/socket.io/socket.io.js" type="text/javascript"></script>
    <script src="./bower_components/angular-route/angular-route.js" type="text/javascript" ></script>
    <script src="./bower_components/angular-messages/angular-messages.js" type="text/javascript" ></script>
    <script src="./bower_components/angular-animate/angular-animate.js" type="text/javascript" ></script>
    <script src="./bower_components/angular-aria/angular-aria.js" type="text/javascript" ></script>
    <script src="./bower_components/angular-material/angular-material.js" type="text/javascript" ></script>
    <script src="./js/app.js"></script>
    <md-toolbar layout="row" flex="1">
      <md-menu-bar>
        <md-menu>
          <md-button ng-href="/#/">Home</md-button>
        </md-menu>
        <md-menu>
          <md-button ng-href="/#/create">Create poll</md-button>
        </md-menu>
        <md-menu>
          <md-button ng-href="/#/view">View Poll</md-button>
        </md-menu>
      </md-menu-bar>
    </md-toolbar>
  <div flex layout="column" layout-align="left" ng-view>
  </div>
</body>
</html>

We will get following output out of this code.

Node and RethinkDB

Here is the Angular code.

/view/js/app.js
var app = angular.module('starterApp', ['ngMaterial','ngRoute','ngMessages']);

app.config(function($routeProvider){
      $routeProvider
          .when('/',{
                templateUrl: 'home.html'
          })
          .when('/create',{
                templateUrl: 'create.html'
          })
          .when('/view',{
                templateUrl: 'view.html'
          });
});

app.controller('pollingController',function($scope,$http,socket) {

  $scope.pollData = [];
  // Load polls when app initialise.
  getPollData();
  /**
    * @function
    * @description fetch the polls from RethinkDB and render it in UI.
  */

  function getPollData() {
    $http.get("/polls").success(function(response){
      $scope.pollData = response.data;
    });
  }
});

As you may have noticed we are using ng-Route to perform the routing at front-end. We need to creating respective files routes to load it in the view respectively.

Related article : Single page routing in AngularJS.

/view/home.html
<md-content flex id="content" layout='column'>
  <md-card ng-repeat='pollInfo in pollData' ng-hide="hiddenrows.indexOf($index) !== -1">
    <md-card-title>
    <md-card-title-text>
        <span class="md-headline">{{pollInfo.question}}</span>
      </md-card-title-text>
    </md-card-title>
    <md-card-content>
    <md-radio-group layout="column" ng-model="pollInfo.selected" ng-change='updateVote($index)'>
      <md-radio-button ng-repeat="polls in pollInfo.polls" ng-value="polls.option" aria-label="{{ polls.option }}">
       {{ polls.option }}
     </md-radio-button>
    </md-radio-group>
    </md-card-content>
  </md-card>
</md-content>

md-card is a Angular material element and we are using ng-repeat to create those cards equivalent to polls.

hiddenrows array and updateVote function we will define in next section.

/view/js/app.js
var app = angular.module('starterApp', ['ngMaterial','ngRoute','ngMessages']);

app.config(function($routeProvider){
      $routeProvider
          .when('/',{
                templateUrl: 'home.html'
          })
          .when('/create',{
                templateUrl: 'create.html'
          })
          .when('/view',{
                templateUrl: 'view.html'
          });
});

app.controller('pollingController',function($scope,$http,socket) {

  $scope.pollData = [];
  $scope.hiddenrows = [];
  getPollData();
  function getPollData() {
    ........................
  }

  $scope.updateVote = function(index) {
    var data = {
      "id" : $scope.pollData[index].id,
      "option" : $scope.pollData[index].selected
    };
    $http.put("/polls",data).success(function(response) {
      if(response.responseCode === 0) {
        $scope.hiddenrows.push(index);
      } else {
        console.log("error");
      }
    });
  }
 
});

updateVote() function will take the vote and hit the API to update it in our RethinkDB. hiddenrows array will contain the index of all those polls which have been voted by the user.

Create new poll screen

In this screen we will have form consist of 4 input box to allow user to create new poll using 3 options. Here is the code for same.

/view/create.html
  <md-content layout-padding>
    <form name="projectForm">
      <md-input-container class="md-block">
        <label>Enter poll question</label>
        <input md-maxlength="70" required name="question" ng-model="formData.pollQuestion">
        <div ng-messages="projectForm.question.$error">
          <div ng-message="required">This is required.</div>
          <div ng-message="md-maxlength">The name has to be less than 70 characters long.</div>
        </div>
      </md-input-container>
      <md-input-container class="md-block">
        <label>option 1</label>
        <input required name="option1" ng-model="formData.pollOption1">
        <div ng-messages="projectForm.option1.$error">
          <div ng-message="required">This is required.</div>
        </div>
      </md-input-container>
      <md-input-container class="md-block">
        <label>Option 2</label>
        <input required name="option2" ng-model="formData.pollOption2"/>
        <div ng-messages="projectForm.pollOption2.$error" role="alert">
          <div ng-message="required">This is required.</div>
        </div>
      </md-input-container>
      <md-input-container class="md-block">
        <label>Option 3</label>
        <input required name="option3" ng-model="formData.pollOption3"/>
        <div ng-messages="projectForm.pollOption3.$error" role="alert">
          <div ng-message="required">This is required.</div>
        </div>
      </md-input-container>
      <md-input-container class="md-block">
        <md-button class="md-raised md-primary" ng-disabled="projectForm.$invalid" ng-click="submitPoll($event)">Create Poll</md-button>
      </md-input-container>
    </form>
  </md-content>

This code will generate following output.

Rethinkdb and Node

On the click event of the button we will call submitPoll() function by passing event as parameter. We will use event parameter to show material dialogue box.

Here is the submitPoll() function.

/view/js/app.js
var app = angular.module('starterApp', ['ngMaterial','ngRoute','ngMessages']);

app.config(function($routeProvider){
      $routeProvider
          .when('/',{
                templateUrl: 'home.html'
          })
          .when('/create',{
                templateUrl: 'create.html'
          })
          .when('/view',{
                templateUrl: 'view.html'
          });
});

app.controller('pollingController',function($scope,$mdDialog,$http,socket) {

  $scope.pollData = [];
  $scope.formData = {};
  $scope.voteData = {};
  $scope.hiddenrows = [];
  getPollData();
  function getPollData() {
      ........................
  }
  $scope.submitPoll = function(ev) {
    var data = {
      "question" : $scope.formData.pollQuestion,
      "polls" : [{
        "option" : $scope.formData.pollOption1, "vote" : 0
      },{
        "option" : $scope.formData.pollOption2, "vote" : 0
      },{
        "option" : $scope.formData.pollOption3, "vote" : 0
      }]
    };
    var message = {"title" : "", "message" : ""};
    // Calling the API to add new poll.
    $http.post('/polls',data).success(function(response) {
      if(response.responseCode === 0) {
        message.title = "Success !";
        message.message = "Poll is successfully created";
        data["id"] = response.data.generated_keys[0];
        // Updating pollData to update the view, this will automatically update and render the new vote at home page.
        $scope.pollData.push(data);
      } else {
        message.title = "Error !";
        message.message = "There is some error happened creating poll";
      }
      // Upon Error or success, dialogue box will appear with message.
      $mdDialog.show(
        $mdDialog.alert()
          .parent(angular.element(document.querySelector('#popupContainer')))
          .clickOutsideToClose(true)
          .title(message.title)
          .textContent(message.message)
          .ok('Got it!')
          .targetEvent(ev)
      );
    });
  }

  $scope.updateVote = function(index) {
      .............................
  }
 
});

View all poll screen

In this screen we will show every poll which has been created by pulling all of them from RethinkDB. We already have $scope.pollData array with every thing we need, all we need to do is to render it in material elements.

Here is the code for same.

/view/view.html
<md-content flex id="content" layout='column'>
  <md-card ng-repeat="pollInfo in pollData">
    <md-card-title>
    <md-card-title-text>
        <span class="md-headline">{{pollInfo.question}}</span>
      </md-card-title-text>
    </md-card-title>
    <md-card-content>
      <md-list>
  <md-list-item class="md-3-line">
    <div class="md-list-item-text" layout="column" ng-repeat="polls in pollInfo.polls">
      <h3 ng-value="polls.option" aria-label="{{ polls.option }}">{{polls.option}} : {{polls.vote}}</h3>
    </div>
  </md-list-item>
  <md-divider ></md-divider>
</md-list>
    </md-card-content>
  </md-card>
</md-content>

This code will generate following output.

Node and rethinkDB

Real time updates push notification using RethinkDB

This is the coolest and my favourite feature of RethinkDB. RethinkDB is capable of finding out changes in table automatically and this can be best used in real time application.

Consider the scenario in our application, after creating the poll user can do the voting. As soon as new vote is added we have to update the “view poll” screen. We will subscribe rethinkDB to our polls table and rethinkDB will keep pushing the changes to us. All we need then to broadcast the changes using Socket.io across connected clients.

Create new file models folder and name it feeds.js and add the following code in it.

/models/feeds.js
var rethinkdb = require('rethinkdb');
var db = require('./db');
var pollObject = new db();
module.exports = function(socket) {
  pollObject.connectToDb(function(err,connection) {
  if(err) {
    return callback(true,"Error connecting to database");
  }
  // Look over this line carefully.
  // we are invoking changes() function on poll table.
  // On every change it will give us data.
  rethinkdb.table('poll').changes().run(connection,function(err,cursor) {
    if(err) {
      console.log(err);
    }
    // We are scrolling over the cursor data and broadcasting the changes using socket.
    cursor.each(function(err,row) {
      console.log(JSON.stringify(row));
      if(Object.keys(row).length > 0) {
        socket.broadcast.emit("changeFeed",{"id" : row.new_val.id,"polls" : row.new_val.polls});
      }
    });
  });
  });
};

This is very important piece of code across all the application. This code will keep us updated and notified about any changes in poll table. All we need to do now is to invoke this function from Express and we are good to go in backend.

app.js
var express = require('express');
var bodyParser = require('body-parser');
var app = express();
var http = require('http').Server(app);
// Adding socket.io
var io = require('socket.io')(http);
var db = require('./models/db');
var feed;
// On connection to the socket, just invoking the function.
io.on('connection',function(socket) {
  feed = require('./models/feeds')(socket);
});

/**
  Adding the controllers.
*/

var dbModel = new db();
dbModel.setupDb();
app.use(bodyParser.json());
app.use(express.static(__dirname + '/view'));
app.use(require('./controllers'));

http.listen(3000, function(){
  console.log('listening on port 3000');
});

And back-end is done, let’s integrate the socket.io in AngularJs and test the complete application.

Integrating socket.io in AngularJS

To integrate Socket.io in AngularJS we will add socket as a factory and listen to the event we are emitting from back-end. Here is the code for same.

/view/js/app.js
var app = angular.module('starterApp', ['ngMaterial','ngRoute','ngMessages']);

app.factory('socket',function(){
  // This is where our app running.
  var socket = io.connect('http://localhost:3000');
  return socket;
});

app.config(function($routeProvider){
      $routeProvider
          .when('/',{
                templateUrl: 'home.html'
          })
          .when('/create',{
                templateUrl: 'create.html'
          })
          .when('/view',{
                templateUrl: 'view.html'
          });
});

app.controller('pollingController',function($scope,$mdDialog,$http,socket) {

  $scope.pollData = [];
  $scope.formData = {};
  $scope.voteData = {};
  $scope.hiddenrows = [];
  getPollData();
  function getPollData() {
      ...............................
  }
  $scope.submitPoll = function(ev) {
      ...............................
  }

  $scope.updateVote = function(index) {
    ...............................

  }

  // This is the event we are emitting from Server.
  socket.on('changeFeed',function(data) {
    // on any change, we will first iterate through our pollData array.
    // If any id matches, we will then update the vote count.
    // $scope.apply will make the change in HTML screen.
    for(var pollCounter = 0 ;pollCounter < $scope.pollData.length; pollCounter++) {
      if($scope.pollData[pollCounter].id === data.id) {
        $scope.pollData[pollCounter].polls = data.polls;
        $scope.$apply();
      }
    }
  });
});

And we have completed the implementation of the app. Let’s test it out.

Testing the app

Run the app by using

node app.js

and visit localhost:3000 from your browser. First create any poll.

Rethinkdb and Node

Open another browser window and visit the view poll screen. Simultaneously from another window, perform the vote. You should be able to see live update in count of vote.


Checkout the console screen too, you can see the live changes in polls table.

Node and rethinkDB

Create more polls and have fun.

Further improvement

To improve the app quality further we can use payload validation in back-end and work on layout of the application in front-end.

Related reading : Payload validation in Node.js

Conclusion

This tutorial is little lengthy than usual tutorials we have in here but i am sure its worth of your time. We tried to take you through every single step of development that we usually go through to develop application before writing tutorials to make you understand the flow better.

RethinkDB is no doubt “next big thing” and with the features such ReQL query language and live feed changes there is big scope of innovation in application development.

The post Building real time polling app using RethinkDB and Nodejs appeared first on Code for geek.