Quantcast
Channel: Jose Luis Monteagudo
Viewing all articles
Browse latest Browse all 13

Realtime JavaScript Stack: nodejs + express + mongoose + socket.io + angularjs

$
0
0

There is a good realtime JavaScript stack, composed by nodejs + express + mongoose + socket.io + angularjs, and I’m going to explain how to integrate these pieces of software to develop a simple web application. The application that I have developed is called VoteExpress, and through this application users are able to vote the team that they think that will win the next Brazil World Cup 2014. When a user votes for a team, every users connected to the page will be notified in realtime that someone has voted that team, and a pie chart will be automatically updated with the new results.

Demo ApplicationSource Code

We can bootstrap our application with the following skeleton: https://github.com/jlmonteagudo/node-express-moongose-min-bootstrap  This skeleton is based on the excellent project developed by Madhusudhan Srinivasa project: Nodejs Express Mongoose Demo, that use best practices developing nodejs web applications.

This is the main tree folder of the skeleton project:

  • app
    • controllers
    • models
  • config
    • config.js
    • db-fixture.js
    • express.js
    • routes.js
    • socket-io.js
  • public
  • server.js

 

The Back End

The back end will manage RESTFUL requests, and these requests will be done by an AngularJS application on the front end, which will be located in the public folder.

The only entity that our application will manage is Team. To manage this entity we only have to create its Schema, its controller and its routes.

Inside the app/models folder we will create team.js, where we will define our model through Moongose (Mongoose Quick Start):

 

var mongoose = require('mongoose')
   ,Schema = mongoose.Schema

var TeamSchema = new Schema({
  code: String,
  name: String,
  urlImage: String,
  votes: {type: Number, default: 0}
})

mongoose.model('Team', TeamSchema)

 

Next, we will create a controller in the folder app/controllers:

 

var mongoose = require('mongoose')
	, Team = mongoose.model('Team')

	exports.list = function(req, res) {
		Team.find({}, function(err, teams) {
	    	res.json(teams);
		});
	}

	exports.update = function(req, res) {
		var team = new Team(req.body);
		Team.update({ _id: team.id }, {votes: team.votes}, function (err, numberAffected, raw) {
			var socketIO = global.socketIO;
			socketIO.sockets.emit('team:updated', team);
			res.json(true);
		});
	}

 

In the controller we define the actions that we will do with our models. list will return a JSON of all the teams with its scores, and update will update the score of the team and will notify about this to all the clients connected to the application.

Finally we have to define the routes in the file config/routes.js:

 

module.exports = function (app) {
     var teams = require('../app/controllers/teams');
     app.get('/teams', teams.list);
     app.put('/teams/:id', teams.update);
}

The Front End

The front end is an AngularJS application that is located in the public/js folder. Its tree folder is as follow:

  • controllers
    • team.js
  • directives
    • chart.js
  • services
    • socketio.js
  • app.js

In app.js we only define the routes:

 

angular.module('voteApp', ['ngResource'])
	.config(function ($routeProvider) {
		$routeProvider
		.when('/', {
	    templateUrl: 'views/team/list.html',
        controller: 'TeamCtrl'
	});
});

 

In the controller we get all the teams that are inside the database, and we define the function vote that will be invoked when someone votes for a team. In the controller also is defined what to do when the server notify that a team has been updated. Here is the controller code:

 

angular.module('voteApp')
	.controller('TeamCtrl', function ($scope, $http, Socket, Team) {

		$scope.disableVote = false;

		//$scope.teams = Team.list();
		$http.get('/teams').success(function(data) {
		$scope.teams = data;
	});

	$scope.vote = function(team) {
		team.votes = team.votes + 1;
			$http.put('/teams/' + team._id, team).success(function(data) {
				$scope.disableVote = true;
			});
		};

	Socket.on('team:updated', function (team) {
		$http.get('/teams').success(function(data) {
			$scope.teams = data;
		});
		$.pnotify({title: 'Vote', text: '+1 vote for ' + team.name });
	});

});

 

We have defined a directive to render the chart (through Google Visualization API):

 

angular.module('voteApp')
	.directive('chart', function () {
		return {
			template: '<div id="chart"></div>',
				restrict: 'E',
				link: function postLink(scope, element, attrs) {

					scope.$watch('teams', function(newValue, oldValue, scope) {

						if (newValue === undefined) return;
						var chartTeams = newValue.map(function(team) {
							return [team.name, team.votes];
						});

						var data = google.visualization.arrayToDataTable(chartTeams, true);
						var googleChart = new google.visualization.PieChart(document.getElementById('chart'));
						googleChart.draw(data);

					});
				}
			};
		});

 

So, to render a chart inside a view we only have to insert the following code:

<chart></chart>

and that tag will render a Pie Chart through the Google Visualization API using the JSON document got from the server.

Finally, I have defined a service (services/socketio.js) that will be used to manage socket.io connections. The code is based on this article  http://www.html5rocks.com/en/tutorials/frameworks/angular-websockets/

 

Conclusion

As we have seen, this is a simple stack that we can use to create our real time applications. In the JavaScript ecosystem there are another systems that allow us to develop this kind of applications, but I think that this one is very flexible.

There are many alternatives to the stack that I have proposed in this article, and two of them that I think that are very interesting are sails.js and Meteor. Maybe I will translate the VoteExpress demo developed in this article to sails and meteor, so we can compare its features.

 

Disclaimer

There are some things in the code that I don’t like and I think that there would be a better way to code. I would be happy if you could propose a better solution.

The first one is that I have defined the socketIO variable as global in the voteExpress/config/socket-io.js file. I know I could pass the socketIO paramater to config/routes and from here to each controller, but I didn’t like the solution.

The second one is related to Angular. I have used Pines Notify, to notify that someone has voted for a team, and I call to it as a JQuery method inside the controller: $.pnotify({title: ‘Vote’, text: ‘+1 vote for ‘ + team.name }); I know that it is a bad practice to update the DOM from the controller, and that it is much better to do that through a directive. Do you have any sugestion to solve this subject in a better way?


Viewing all articles
Browse latest Browse all 13

Trending Articles