Our Thoughts

Full-Stack JavaScript

Share this:

Earlier this month, we received an interesting requirement from one of our existing customers. The requirement was to build a fairly simple web application, with one catch “The solution should not use IIS or Apache but use a custom web server”. After further discussions with the customer we understood that the customer had bad experiences in terms of performance and resource usage when deploying solutions hosted on IIS / Apache. The root cause was that the hardware being used to host these web servers were closer to the Minimum Hardware Requirements, resulting in High CPU & Memory usage and a perceived performance issue. These slow performing applications resulted in low adoption rates with the application users, which is why the explicitly stated “custom web server”.

Fair enough, the customer had a limitation where the hardware could not be scaled up and needed a solution which can be hosted on the existing hardware. In most cases, we would give up the case, inferring budget constraints for hardware would mean no budget for software. However, we took this as an opportunity to look beyond the .NET, Java & PHP world to meet the customer’s requirements. We needed a solution that would have a low memory footprint on the server and judiciously use the CPU. We had heard a lot about Node.JS and its performance capabilities and decided to explore the Full-Stack further. The most popular stack seemed to be MEAN (Mongo DB, Express, Angular & Node). Well the customer, wanted the database to be on MySQL, so we looked at creating a proof of concept on MySQL, Express, Angular & Node. The objective of the proof of concept was to see how the Full-Stack performs in terms of Page Load Times & Server Resource Usage (CPU & Memory). The results were very promising and more importantly did not take more than two days to setup & develop.

During the study phase, there were other supporting technologies that were used to speed up the development. The components and the system architecture are shown below.

 mean_full_stack

Angular JS – A JavaScript Client Side framework.

Node JS – Event Driven I/O Server Side JavaScript environment based on V8.

Express – A Node JS web application server framework to build web applications & API.

Sequelize – A Node JS ORM for MySQL

MySQL – A Relational Database Management System

The example chosen for the proof-of-concept was a basic application with Create & Read operations for a list of users. The steps followed to complete the example are detailed below:

Step 1: Install Node.js on your machine.
Step 2: Create table using following script on your MySQL database.

CREATE TABLE `users` (
  `Id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `UserName` varchar(50) COLLATE utf8_unicode_ci NOT NULL,
  `password` varchar(20) COLLATE utf8_unicode_ci NOT NULL,
  `Address` varchar(150) COLLATE utf8_unicode_ci DEFAULT NULL,
  `PhoneNo` varchar(45) COLLATE utf8_unicode_ci DEFAULT NULL,
  `MobileNo` varchar(45) COLLATE utf8_unicode_ci DEFAULT NULL,
  `EmailID` varchar(45) COLLATE utf8_unicode_ci DEFAULT NULL,
  `Designation` varchar(45) COLLATE utf8_unicode_ci DEFAULT NULL,
  `Company` varchar(45) COLLATE utf8_unicode_ci DEFAULT NULL,
  PRIMARY KEY (`Id`)
) 

Step 3: Create a root folder for your project files and create the package.json file
This file allows Node.js to know the project dependencies. Once created, execute the Node Package Manager(npm) Install command to fetch and install the modules.


{	"name": "node-api",
	"main": "server.js",
	"dependencies": {
		"express": "~4.0.0",
		"body-parser": "~1.0.1",
		"mysql": "~2.5.0",
		"sequelize": "~1.7.0",
		 "cors":"~2.7.1"
	}
}

Step 4: Navigate to your root folder using Node.js command prompt and execute the command.


> npm install

step 5: Create database.json file for database configuration.


{
  "dev": {
    "driver": "mysql",
   "host":"localhost",
   "port":"3306",
    "user": "root",
    "database": "CRM",
    "password": "dbpassword"
  }
} 

Step 6 : Create a new file server.js in your root folder and initialize the modules ‘express’, ‘body-parser’ & ‘cors’. You also need to set your environment variables and port.


var express = require('express'),
bodyParser = require('body-parser');
var app = express();
app.use(bodyParser());
var cors = require('cors');
app.use(cors());
var env = app.get('env') == 'development' ? 'dev' : app.get('env');
var port = 8080;

Step 7: Sequelize is used as the ORM in this example. The below section reads configurations from the database.json file to establish a connection to the database.

var Sequelize = require('sequelize');
// db config
var env = "dev";
var config = require('./database.json')[env];
var password = config.password ? config.password : null;
// initialize database connection
var sequelize = new Sequelize(
	config.database,
	config.user,
	config.password,
	{
	host:config.host, 	
	port:config.port,
    dialect: config.driver,
    logging: console.log,
		define: {
			timestamps: false
		}
	}
);

Step 8: Define a model User and a few methods for CRUD operations.


var User = sequelize.define('users', {
    UserName: DataTypes.STRING,
    Password: DataTypes.STRING,
	Address: DataTypes.STRING,
	PhoneNo:DataTypes.STRING ,
	MobileNo:DataTypes.STRING,
	EmailID:DataTypes.STRING ,
	Designation:DataTypes.STRING,
	Company:DataTypes.STRING
  }, {
    instanceMethods: {
      retrieveAll: function(onSuccess, onError) {
			User.findAll({ }, {raw: true}).success(onSuccess).error(onError);
	  },
	    retrievePage: function(record_count,onSuccess, onError) {
			User.findAll({ limit: record_count }, {raw: true}).success(onSuccess).error(onError);
	  },
      retrieveById: function(user_id, onSuccess, onError) {
			User.find({where: {id: user_id}}, {raw: true}).success(onSuccess).error(onError);
	  },
      add: function(onSuccess, onError) {
			var username = this.username;
			var password = this.password;
			  this.save().success(onSuccess).error(onError);
	   },
	  updateById: function(user_id, onSuccess, onError) {
			var id = user_id;			
			User.update({ username: username,password: password},{where: {id: id} }).success(onSuccess).error(onError);
	   },
      removeById: function(user_id, onSuccess, onError) {
			User.destroy({where: {id: user_id}}).success(onSuccess).error(onError);
	  }
    }
  });

Step 9: Now define routes for Url Routing and register the routes.


var router = express.Router();
// on routes that end in /users
router.route('/users')
// create a user (accessed at POST http://localhost:8080/api/users)
.post(function(req, res) {
	var username = req.body.username; //bodyParser does the magic
	var password = req.body.password;
	var address = req.body.Address;
	var mobileno = req.body.MobileNo;
	var emailID = req.body.EmailID;
	var designation = req.body.Designation;
	var company = req.body.Company;
	var user = User.build({ UserName: username, Password: password, Address:address, MobileNo:mobileno, EmailID:emailID, Designation:designation, Company:company });
	user.add(function(success){
		res.json({ message: 'User created!' });
	},
	function(err) {
		res.send(err);
	});
})
// get all the users (accessed at GET http://localhost:8080/api/users)
.get(cors(),function(req, res) {
	var user = User.build();
	user.retrieveAll(function(users) {
		if (users) {
		  res.json(users);
		} else {
		  res.send(401, "User not found");
		}
	  }, function(error) {
		res.send("User not found");
	  });
});
//get all the users by rowcount
router.route('/users/:count')
.get(cors(),function(req, res) {
	var user = User.build();
	user.retrievePage(req.params.count,function(users) {
		
		if (users) {
		  res.json(users);
		} else {
		  res.send(401, "User not found");
		}
	  }, function(error) {
		res.send("User not found");
	  });
});
// on routes that end in /users/:user_id
// ----------------------------------------------------
router.route('/user/:user_id')
// update a user (accessed at PUT http://localhost:8080/api/users/:user_id)
.put(function(req, res) {
	//Code to update User
	  }, function(error) {
		res.send("User not found");
	  });
})

// get a user by id(accessed at GET http://localhost:8080/api/users/:user_id)
.get(function(req, res) {
	var user = User.build();

	user.retrieveById(req.params.user_id, function(users) {
		if (users) {
		  res.json(users);
		} else
 {		  res.send(401, "User not found");
		}
	  }, function(error) {
		res.send("User not found");
	  });
})
// delete a user by id (accessed at DELETE http://localhost:8080/api/users/:user_id)
.delete(function(req, res) {
	//Code to delete
		}
	  }, function(error) {
		res.send("User not found");
	  });
});
// Middleware to use for all requests
router.use(function(req, res, next) {
	console.log('Something is happening.');
	next();
});
app.use(function (req, res, next) { 
    next();
});
// REGISTER OUR ROUTES
app.use('/api', router);
app.listen(port);
console.log('server started on port ' + port);

Step 10: Our REST API with CRUD capabilities is ready and the following command will make it available over the defined port.

> node server.js

Step 11: In order to retrieve data and show it in a grid we will use HTML with AngularJS


<html lang="en-US">
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js"></script>
<body>
<Head> Create New User </Head>
<form ng-app="myApp" ng-controller="userCtrl" ng-submit="processForm()" >
<div > 
<style>
table, td, tr {
    border: 1px solid black;
}
tr {
    background-color: gray;
    color: white;
}
</style>
<table style="widtr:90%;">
<tr><td><Label>USER NAME</Label></td><td><input type="text" ng-model="formData.username" ></input><td></tr>
<tr><td><Label>Password</Label></td><td><input type="text" ng-model="formData.password" ></input><td></tr>
<tr><td><Label>Address</Label></td><td><input type="text" ng-model="formData.Address" ></input><td></tr>
<tr><td><Label>MobileNo</Label></td><td><input type="text" ng-model="formData.MobileNo" ></input><td></tr>
<tr><td><Label>Email ID</Label></td><td><input type="text" ng-model="formData.EmailID" ></input><td></tr>
<tr><td><Label>Designation</Label></td><td><input type="text" ng-model="formData.Designation" ></input><td></tr>
<tr><td><Label>Company</Label></td><td><input type="text" ng-model="formData.Company" ></input><td></tr>              
<tr><td></td><td><button type="submit" style="width:50%; align:right;"  >Save</button><td></tr>
<tr><td></td><td><label type="submit" style="width:50%; align:right;"  >{{message.message}}</label><td></tr>   
</table>
<label></label>
</div>
<script>
var app = angular.module('myApp', []);
app.config(['$httpProvider', function($httpProvider) {
        $httpProvider.defaults.useXDomain = true;
        delete $httpProvider.defaults.headers.common['X-Requested-With'];
    }
]);
app.controller('userCtrl', function($scope, $http) {
	   
	 $scope.formData = {};

    // process the form
    $scope.processForm = function() {
	$http.post("http://[server]:8080/api/users",$scope.formData)
    .success(function(response) {
	console.log(response);

	$scope.message = response;
});
}
});
</script>
</body>
</html>

You can host the HTML page by adding the following lines of code to server.js


var connect = require('connect');
var serveStatic = require('serve-static');
connect().use(serveStatic(__dirname)).listen(8081);
console.log('html started on port ' + 8081);

To get a limited no. of rows you can change the API request URL to include the count parameter
$http.get(“http://[server]:8080/api/users/[count]”)

This completes the Client Side code and you can now benchmark the performance. For a machine with 4 GB RAM, AMD Athlon 3.4 Ghz processor and Windows 8 it takes an average of 160 ms for 100 records. This timing includes data fetch and page rendering.

Other Posts

Leave a Reply

Your email address will not be published. Required fields are marked *

Categories