Nodemap – First product of my Nodejs playground

  • Sharebar

I started learning Nodejs few weeks back and fell in love with the way it eases the development of real-time web applications. In general, when I start learning a new technology I try to make an app out of that learning. In that process, the most challenging task is "What to develop?" rather than How to develop? Finally an interesting app idea came into my mind and I was able to turn that idea into an application. So here I am explaining the app and its code in detail.

NodeMap : A realtime Nodejs application with Express, Nowjs, Ejs, Google Maps, Mongodb hosted on Joyent SmartMachines. What does this app do? It simply shows real-time messages on the world map from different users around the world in an awesome way.
Click Here To See Working App

First things first, the technology aspect :

  • Why Nodejs? Because its awesome and amazingly fast server side javascript.(For more info refer Nodejs Homepage).
  • Why Express? Because I wanted a Web-MVC and this one is the best out there, with community support.(For more info refer Expressjs Web MVC)
  • Why Nowjs? Because it allows me to "Call client functions from the server and server functions from client".(It internally uses socket.io, for more info refer Nowjs)
  • Why ejs? Because this template engine is very similar to html. So it was easy to grasp.(low learning curve For more info refer EJS)
  • Why Google Maps? Because I wanted to show some maps in my app. (For more info refer Google Maps API v3)
  • Why Mongodb? Because I wanted a light-weight NoSQL database which can easily run on Joyent SmartMachines.(For more info refer Mongodb)

Lets deep dive into the application code, here is the link to Github Repository.

In a real-time web application both server side and client side code plays vital role in the proper functioning of the applicaiton. So I will switch between server-side code and client-side code while explaining the code.

Lets start with the server side snippet below :

//Module dependencies.
var express = require('express'), nowjs = require('now'), mongojs = require('mongojs');
//starting server
var app = express.createServer().listen(80);
//Server configuration
app.configure(function(){
  app.set('views', __dirname + '/views');
  app.set('view engine', 'ejs');
  app.use(express.bodyParser());
  app.use(express.methodOverride());
  app.use(express.static(__dirname + '/public'));
});
//Initialize mongojs to connect with mongodb
var db = mongojs.connect('nodemapdb', ['mycollection']);
//Initialize nowjs for real-time client-to-server and server-to-client calls
var everyone = nowjs.initialize(app, {socketio: {transports: ['xhr-polling', 'jsonp-polling']}});

Server side code starts with including the required dependencies( which are expressjs, nowjs and mongojs) and configuring them appropriately. Lets have a look at each one of them :
Express
Line 4 creates a HttpServer to listen on port 80.
Line 6 starts with configuration of the server.
Line 7 sets the views directory path.
Line 8 sets the view engine to ejs (if we don't set it the default jade view engine will be used).
bodyParser() parses the request body and populates req.body
methodOverride() checks req.body.method for the HTTP method override(i.e override to GET/POST/PUT etc.)
Line 11 sets the static resource directory which will serve the static resources to the client.
Thats all with express(for more info. please refer Express Guide)

 

Mongojs
Line 14 initializes mongojs and configures it to connect with database 'nodemapdb' and collection 'mycollection'. For more configuration options refer : Mongojs Read Me

Nowjs
Line 16 initializes nowjs to work with the current HttpServer and explicitly set the socket.io transport to xhr or json polling. The second parameter is optional and by default uses websockets as first priority. I have explicitly set it because my application host does not support websockets on free accounts.

Now lets see how the request to home page is handled :

app.get('/', function(req, res){
	var dataArray=[];
	db.mycollection.find({},{_id:0}).forEach(function(err, doc) {
		if (!doc) {
			// we visited all docs in the collection
			res.render('index', { title: 'NodeMap : Using Nodejs,Nowjs,Mongodb & Google Maps ' , messages : JSON.stringify(dataArray)});
			return;
		}
		dataArray.push(doc);
	});
});

Whenever a person hits http://nodemap.no.de the above app.get("/",...) method will be called. What I am doing here is, simply getting all the documents stored in the database(without the "_id" field). And for each document found, it is pushed in "dataArray" and finally a call to res.render which will render the index.ejs in the views directory, and, since I haven't set "layout : false" while configuring the server, the contents of index.ejs will be rendered as "body" of layout.ejs(the concept of layouts in express is similar to concept of master-pages).

 

Now lets see how the initial markers are set on the map(client-side code):

<body onload="initialize();">
<div id="map_canvas" ></div>
<div id="form-wrapper" style="z-index: 1000002; position: absolute; right: 0px; bottom: 0px; ">
	<div id="form1">
		Name : <input id="owner" type="text" /><br/>
		Message : <input id="message" type="text" /><br/>
		<input id="locationCheck" type="checkbox" >Use my location</input><br/>
		<div id="cord" style="display:none;">
			Latitude : <input id="latitude" type="text" /><br/>
			Longitude : <input id="longitude" type="text" />
		</div>
		<input type="button" value="Submit" onclick="getGeoLocation();"/>
	</div>
</div>
<div id="messages" style="display:none;"><%=messages%></div>
</body>

<script>
function initialize() {
	var mapOptions = {
		maxZoom: 9,
		minZoom:2,
		zoom: 2,
		center: new google.maps.LatLng(27, 30),
		mapTypeControl: false,
		streetViewControl: false
	};
	var mapType = new google.maps.StyledMapType([
	    {
		featureType: "all",
		elementType: "all",
		stylers: [
		    { visibility: "off" },  // Hide everything
		    { lightness: 100 }  // Makes the land white
		]
	    }, {
		featureType: "water",
		elementType: "geometry",
		stylers: [
		    { visibility: "on" },  // Show water, but no labels
		    { lightness: -9 },  // Must be < 0 to compensate for the "all" lightness
		    { saturation: -100 }
		]
	    }
	]);
	map = new google.maps.Map(document.getElementById("map_canvas"), mapOptions);
	map.mapTypes.set('styledMapType', mapType);
	map.setMapTypeId('styledMapType');

	var messages = JSON.parse($("#messages").html());
	var size = messages.length;
	for(var i=0; i<messages.length; i++){
		message = messages[i];
		if(size -i < 50 ) {
			setTimeout((function(msg) {
				return function() {
				    addMarker(msg);
				}
		    	})(message), i*400 + Math.random()*500+500);
		}else{
			addMarkerDefault(message);
		}
	}
}
function addMarker(message){
	var marker = new google.maps.Marker({
		map: map,
		animation: google.maps.Animation.DROP,
		position: new google.maps.LatLng(message.latitude,message.longitude),
		icon: image
	});
	var infowindow = new google.maps.InfoWindow({
		content: message.owner+" : "+message.message
	});
	addNewListener(map,marker,infowindow);
}
</script>

The above code does some important things :

 

  • The code at line 2, assigns the messages data passed in res.render(server side) call to the div with "id=messages".
  • Code in the script tag, initializes Google Maps with some styling applied to it,
  • And then parses the JSONified documents and places a marker on the Google Map corresponding to that document.

This completes the initial rendering of the home page. Now lets see some interesting stuff, how I call server-side declared method from client side. For that, see the code below:

//written server-side
everyone.now.addMarkerServer = function(msg){
	console.log(msg);
	db.mycollection.save({	owner : msg.owner,
			message : msg.message,
			latitude : msg.latitude,
			longitude: msg.longitude
		}, function (err, res) {
		// Handle response
		console.log(res);
	});
	everyone.now.addMarkerClient(msg);
};

//written client side
now.ready(function(){
	now.addMarkerClient = function(data) {
		addMarker(data);
	};
});

NowJS uses the excellent socket.io and node-proxy libraries. It creates a namespace "now", accessible by server and client. Functions and variables added to now are automatically synced, in real-time. Whenever user submits a message, now.addMarkerServer(msg) is called which actually calls the server-side declared addMarkerServer function.

 

In addMarkerServer, the data passed is saved in the database and it calls everyone.now.addMarkerClient(msg); with the same data. Any method server calls to the everyone.now namespace gets called on every client's side(there can be thousands of client).

So in this blog post we learned how cool nodejs is and how easy it is to develop real time applications using nodejs. Although, I have used it very naively in my small application but nodejs has already entered the enterprise. LinkedIn's mobile website's serverside is completely built on nodejs(link). Even Wallmart is set to use nodejs(link).

Happy Reading.

Share the bee buzz:
  • Digg
  • del.icio.us
  • Facebook
  • DZone
  • LinkedIn
  • StumbleUpon
  • Technorati
  • Twitter

12 Responses to “Nodemap – First product of my Nodejs playground”

  1. Very cool application. Liked it a lot. Will look forward to learn node.js from you.

    Thanks
    Shekhar

  2. Hi Shekhar,
    I will be giving an XKE on Nodejs very soon.

    Thanks
    Vijay

  3. [...] Nodemap – First product of my Nodejs playground. [...]

  4. Cool. Works like a charm.

    Thanks.

  5. Recent Blogroll Additions……

    [...]usually posts some very interesting stuff like this. If you’re new to this site[...]……

  6. Qalqi@Devadatta:~/Projects/JavaScript/nodeJS/vijayrawatsan-nodemap-6a26624$ node server.js
    Express server listening on port 80
    { [Error: listen EACCES] code: ‘EACCES’, errno: ‘EACCES’, syscall: ‘listen’ }
    Error: listen EACCES
    at errnoException (net.js:670:11)
    at Array.1 (net.js:756:28)
    at EventEmitter._tickCallback (node.js:192:40)

    While running the server code I was getting this error. Would you please have alook at it,Vijay.

  7. This seems to be an access error. Assuming you are running on you personal machine. Try running on a different port(like 8000). It should work. If same error comes try running as a root.

  8. I getting following error.

    node server.js
    Express server listening on port 80
    warn – error raised: Error: listen EACCES

    can you pls help me to run this code vijay.

  9. now i am able to run it on port 8000, still getting error related to express.

    Express
    500 Error: Cannot find module ‘ejs’
    at Function._resolveFilename (module.js:334:11)
    at Function._load (module.js:279:25)
    at Module.require (module.js:357:17)
    at require (module.js:368:17)
    at View.templateEngine (/Users/rmudale/nodetest/node_modules/express/lib/view/view.js:134:38)
    at Function.compile (/Users/rmudale/nodetest/node_modules/express/lib/view.js:68:17)
    at ServerResponse._render (/Users/rmudale/nodetest/node_modules/express/lib/view.js:417:18)
    at ServerResponse.render (/Users/rmudale/nodetest/node_modules/express/lib/view.js:318:17)
    at /Users/rmudale/nodetest/server.js:46:8
    at /Users/rmudale/nodetest/node_modules/mongojs/node_modules/common/index.js:100:4

  10. Hi Rajendra,

    This seems to be a installation problem..
    Please have a look at :
    http://stackoverflow.com/questions/7754799/error-cannot-find-module-ejs

  11. Thanx for the reply.. yes there as an installation problem with ejs.

    can we extend this code to share the map as well (i mean share the map with zoom out & zoom in features. If one user zooms in, it should be emitted to other users as well).

  12. Well I haven’t tried that. You might need to check Google Maps API for that.
    :)

Leave a Reply