Node.js

In the previous article, I gave an example how to make a Node.js application that can host a simple web application files. Basically, Node.js is not an application that build only for web development, but one of its modules (the HTTP module), could help us much in developing a web application server using Node.

Like the previous article, this article uses the v0.5.9 of Node.js windows executable program that can be downloaded here.
 

Handle the Request

A Node application is an application that always has its instance until the application exit. Optimizing that feature, this application will read a file that contains user list data (can be replaced with a table in database in real implementation) and store the data in a variable as an array.

Create a “Server.js” file, then insert this following code.

var http = require('http'),
	url = require('url'),
	sys = require('util');

var handler = require('./lib/Handler.js');

http.createServer(function(request, response) {

	try {
		console.log('Request from: ' +
			request.connection.remoteAddress +
			' | href: ' + url.parse(request.url).href);

		handler.handle(request, response);

	} catch (e) {
		sys.puts(e);
		response.writeHead(500);
		response.end('Internal Server Error');
	}

}).listen(8087, "127.0.0.1", function () {
	console.log("Server running at http://localhost:8087/");
});

 

This article still use the same “Server.js” file with the previous article.

 

This program will handle every request that received from clients with the handle function that located in Handler library.

handle(request, response)

 

You also need to create “Handler.js” under directory ‘lib’. This “Handler.js” file is different with the “Handler.js” file in the previous article. The differences are shown by the comments on them.

var fs = require('fs'),
	path = require('path'),
	// load the User class
	mUser = require('./../model/User');
	
// create the instance of User class
var User = new mUser.User();

this.handle = function (request, response) {
	var renderHtml = function (content) {
		response.writeHead(200, { 'Content-Type': 'text/html' });
		response.end(content, 'utf-8');
	};

	var notFound = function () {
		response.writeHead(404, { 'Content-Type': 'text/plain' });
		response.end("404 Not Found\n");
	};
	
	// function that return JSON object
	var respJSON = function (response, object) {
		response.writeHead(200);
		response.end(JSON.stringify(object));
	};

	var parts = request.url.split('/');
	var rest = parts.slice(2).join('/');
	var page_rest = parts.slice(1).join('/');

	if(request.url == '/') {
		fs.readFile('./web/index.html', function (error, content) {
			if(error) {
				notFound();
				return;
			}
			else {
				renderHtml(content);	
			}
		});
	}
	// check if the requested URL started with 'action', it means that
	// the request is an ajax request
	else if(parts[1] == 'action') {
		// check whether the request method is a POST method
		if(request.method === 'POST') {
			// get the data in request body
			var temp = '';
			request.on('data', function (chunk) {
				temp +=	chunk;					 
			});
			
			request.on('end', function() {
				var data = {};
				var action = parts[2];
				
				data = JSON.parse(temp);
				console.log('action to \'' + action + '\', data: ' + JSON.stringify(data));
				
				// if the action is login
				if(action == 'login') {
					// initialize 'resp' variable that will be used to be the response
					var resp = { success: false, message: '' };
					
					// simple validation
					if(data.Username == '') {
						resp.message = 'Username must be filled';	
					}
					else if(data.Password == '') {
						resp.message = 'Password must be filled';	
					}
					else {
						// you can review the User class in the file 'model/User.js'
						// call the login function
						User.doLogin(data.Username, data.Password, function (userData) {
							// callback for success
							resp.result = true;
						}, function() { resp.message = 'Login failed'; });
					}
					
					// return the resp variable as response
					respJSON(response, resp);
				}
				// you can add other additional actions here
			});
		}
		else {
			notFound();
		}
	}
	else if(parts[1] != 'css' && parts[1] != 'images' && parts[1] != 'js') {
		fs.readFile('./web/' + page_rest, function (error, content) {
			if(error) {
				notFound();
				return;	
			}
			else {
				renderHtml(content);	
			}
		});
	}
	else {
		path.exists('./web/' + parts[1] + '/' + rest, function(exists) {
			if(!exists) {
				notFound();
				return;
			}

			fs.readFile('./web/' + parts[1] + '/' + rest, 'binary', function(error, content) {
				response.writeHead(200);
				response.end(content, 'binary');
			});
		});
	}
};

 

The main difference is, this “Handle.js” has a new action for a requested URL like ‘/action/[something]’. This “Handle.js” will do action based on the [something] word. In the example above, “Handle.js” only handle the ‘action’ request if it’s a POST-method request and the [something] word is ‘login’.
It also load a model object that comes from “User.js”, this will be explained later.
 

User Model

Create a “User.js” file under the ‘model’ directory. This object will load the User data file (in this example is a ‘User.dat’ file that located under the ‘data’ directory.

// thanks to jaeckel for this FileLineReader :)
// http://blog.jaeckel.com/2010/03/i-tried-to-find-example-on-using-node.html
var flr = require('./../lib/FileLineReader');

exports.User = function() {
	var data = {};

	// initialize the data
	try {
		var reader = new flr.FileLineReader('./data/Users.dat');
		// generate as hash table
		while(reader.hasNextLine()) {
			var line = reader.nextLine();
			var res = line.split(';');
			if(res.length!=3) return false;
			data[res[0]] = { Username: res[0], Password: res[1] };
		}
		reader.close();
	} catch (e) {
		console.log(e.toString());
		return false;
	}
	
	this.doLogin = function(username, password, onSuccess, onFailure) {
		// check the hash table
		if(typeof(data[username])!=='undefined') {
			// if the password is right, call the onSuccess function
			if(data[username].Password == password) onSuccess(data[username]);
			// else call onFailure function
			else onFailure();
		}
		else {
			onFailure();	
		}
	}
	
	return this;
};

 
This is the “User.dat” file I used under the ‘data’ directory.

testuser;password 1;
foouser;foo;

 

I also want to say thanks to Jaeckel, I use his “FileLineReader” to read line by line my “User.dat” file. You can get the “FileLineReader” detail here.
 

Login Page

I use a simple login page that send request using jquery ajax.

$(document).ready(function () {
		$('#LoginForm_btnSubmit').click(function (e) {
			$.ajax({
				type: "POST",
				url: 'action/login',
				contentType: 'text/json',
				dataType: "json",
				data: JSON.stringify({ Username: $('#LoginForm_username').val(), Password: $('#LoginForm_password').val() }),
				success : function(result) {
					if(result.result) {
						document.location.href = 'main';
					}
					else {
						$('#LoginForm_message').html(result.message);
					}
				},
				error: function(jqXHR, textStatus, errorThrown) { alert("Internal Server Error"); }	
			});									  
		});
	});

(I’m sorry that I can’t show the full version of the “login.html” file because of some limitations of showing code in my blog)
 

Conclusion

Using this concept of signing in, this application will only read the User data file (or database) once (in best performance where there is no data updated). Checking whether the data exists or not only need to check the hash table of the user data. But if the data of the users is too large, it will make the server uses more spaces in memory.
 

You can download the full example here.