Building Realtime Apps using Node/Socket.IO, Mongo and Angular

Presented for   NEAN-Meetup #2


Created   by   Gautam Anand   


"Looking for Speakers for the community, Buzz - me@gautamanand.in"







Before we start :



Github : 

Presentation :


Last meetup Presentation to clear basics :



Socket.IO 

http://socket.io/docs/#using-with-the-express-framework

$ npm install socket-io

  • Realtime and Bi-directional communication b/w Client and server
  • Client Library - chat.js
  • Server side Library - Routes.js  (for Nodejs)
  • Event-driven 
  • Web sockets API + Handle browser
  • Downgrade mentality - works on old browsers
  • Works with AngularJS

Socket.IO will use feature detection to decide if the connection will be established with WebSocket, AJAX long polling, Flash, etc., making creating realtime apps that work everywhere a snap. Socket.IO also provides an API for Node.js which looks very much like the client side API.

Realtime for SPA : Node FrameW





Min-set for MEAN Stack


  • Change to NODE + SOCKET.IO

Structure

  1. Public - All the Client code is here
  2. Views - The views of your MVC app. EJS/HTML/Jade
  3. Node_modules - all the extra node npm 
  4. app.js - the Node server config
  5. config.js - database, Socket configs
  6. routes.js - REST API, Socket resources
  7. package.json - npm package manager
  8. /public/bower.json - package manager for frontend

$ npm install generator-node
$ yo node
  • Express cmdline 
 $ express -e App_name
$ git clone https://github.com/angular/angular-seed.git
$ cd angular-seed

Stack 


  • Nodejs - Backend and REST API
  • Socket.IO - Realtime stuff
  • Gravatar npm - Personalize
  • MongoDB - Database that saves JSON as it is
  • Angular - Rockstar frontend MVC



    Socket.IO saves the Username, Gravatar and Room in Routes.js as properties.

Socket.IO Room features is used to create private chat hubs.
more on namespaces & rooms : http://socket.io/docs/rooms-and-namespaces/

Package.json

#package.json is a node package manager that manages all the packages.
 {
  "name": "NEANRealtimeChat",
  "version": "0.0.1",
  "main": "app.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
 
  "dependencies": {
    "express": "~3.5.0",
    "gravatar": "~1.0.6",
    "socket.io": "~0.9.16",
    "ejs": "~0.8.5"
  }
}
 $npm install

# Go to the local directory and update the npm. All are stored in node_modules

app.js


 var express = require('express'),
	app = express();

var port = process.env.PORT || 8080;

var io = require('socket.io').listen(app.listen(port));

require('./config')(app, io);
require('./routes')(app, io);

console.log('Check the app at Port :' + port);
  • Simple and straight forward
  • Call express
  • Define port - 8080
  • Define IO to call Socket lib
  • See Config.js for Socket+express middlewares
  • See Routes.js for path
  • CMD to show the server has started

Config.js


 var express = require('express');

module.exports = function(app, io){
	app.set('view engine', 'html');
	app.engine('html', require('ejs').renderFile);
	app.set('views', __dirname + '/views');
	app.use(express.static(__dirname + '/public'));
	io.set('log level', 1);

};






  • Define Express middle-wares - Session, error handling etc (Lot can be added)
  • Specs :
  1. HTML rendering engine as ejs
  2. See "Views" folder for all these files
  3. See the "Public" Folder for common resources
  4. Since we pass the web-server to socket, we log it too for changes.

routes. js - Structure



 var gravatar = require('gravatar');

module.exports = function(app,io){

// The code comes here that is discussed in next few slides

};


  1. Define Gravatar npm
  2. REST API - get for specific resources
  3. Chat Socket config 

routes.js - REST API

GET-PUT-POST-DELETE == "Read-Update-Create-Delete"

 app.get('/', function(req, res){
		res.render('home');
	});

 app.get('/create', function(req,res){
		var id = Math.round((Math.random() * 1000000));
		res.redirect('/chat/'+id);
	});

app.get('/chat/:id', function(req,res){
		res.render('chat');
	});

  1. Get for '/' - render "/views/home.html"
  2. Get for '/create' - Create a random ID, redirect to '/chat'
  3. Get for '/chat/:id' - render "/views/chat.html"

routes.js - Socket config


 var chat = io.of('/socket').on('connection', function (socket) {   

//All the socket.IO config comes here 

});
  • Create chat variable that will pass when socket is open

We would define 4 cases :
  1. Load - When loaded do this
  2. Login - When login on do this
  3. Disconnect - When disconnected do this
  4. msg - When login pass this message

routes.js - Socket load

 	socket.on('load',function(data){

			if(chat.clients(data).length === 0 ) {

				socket.emit('peopleinchat', {number: 0});
			}
			else if(chat.clients(data).length === 1) {

				socket.emit('peopleinchat', {
					number: 1,
					user: chat.clients(data)[0].username,
					avatar: chat.clients(data)[0].avatar,
					id: data
				});
			}
			else if(chat.clients(data).length >= 2) {

				chat.emit('tooMany', {boolean: true});
			}
		});
  • If else case - When clients :0/1/2+ == show a user/Show all details of new user / Broadcast to all users that its full
  • Socket.emit() - Socket.send() + registers to msg

routes.js - Socket login

 socket.on('login', function(data) {

			if(chat.clients(data.id).length < 2){

				socket.username = data.user;
				socket.room = data.id;
				socket.avatar = gravatar.url(data.avatar, {s: '140', r: 'x', d: 'mm'});

			socket.emit('img', socket.avatar);


				socket.join(data.id);

				if(chat.clients(data.id).length == 2) {

					var usernames = [],
						avatars = [];

					usernames.push(chat.clients(data.id)[0].username);
					usernames.push(chat.clients(data.id)[1].username);

					avatars.push(chat.clients(data.id)[0].avatar);
					avatars.push(chat.clients(data.id)[1].avatar);


					chat.in(data.id).emit('startChat', {
						boolean: true,
						id: data.id,
						users: usernames,
						avatars: avatars
					});
				}

			}
			else {
				socket.emit('tooMany', {boolean: true});
			}
		});
  1. If less than 2 users - Show details to invite others on same socket
  2. if two users - Show the chat for them 
  3. Of more than two - Show a message stating that You cannot chat in this room.

routes.js - Socket disconnect


 socket.on('disconnect', function() {

			socket.broadcast.to(this.room).emit('leave', {
				boolean: true,
				room: this.room,
				user: this.username,
				avatar: this.avatar
			});

			socket.leave(socket.room);
		});


  • When a user leaves then show he has left and exit it from rooms
  • Show message, this chat room is now free. You can join.

routes.js - Socket msg




 socket.on('msg', function(data){

			socket.broadcast.to(socket.room).emit('receive', {msg: data.msg, user: data.user, img: data.img});
		});



  • When someone posts a message then broadcast to room
  • Also to all user in room

Chat.js - Socket Structure


  1. /public/js/chat.js
  2. Get url of the room
  3. Define Socket and other Client-side variables. - Socket.on("prop",fun())
  4. Load - On connection to server get the id of person's room
  5. img - Save the gravatar url
  6. peopleinchat - receive the names and avatars of all people in the chat room
  7. Starchat- Show the banner or msg
  8. leave - Disconnect and leave a chat
  9. toomany - Show that the room is full.
  10. receive - Input msg
  11. Submit - Submit a msg
  12. SetInterval - timestamp for msg sent
  13. createmessage - Combine all the parameters
  14. showmessage - publish with all parameters

chat.js - peopleinchat

 socket.on('peopleinchat', function(data){

		if(data.number === 0){

			showMessage("connected");

			loginForm.on('submit', function(e){

				e.preventDefault();

				name = $.trim(yourName.val());

				if(name.length < 1){
					alert("Please enter a nick name longer than 1 character!");
					return;
				}

				email = yourEmail.val();

				if(!isValid(email)) {
					alert("Please enter a valid email!");
				}
				else {

					showMessage("inviteSomebody");

					// call the server-side function 'login' and send user's parameters
					socket.emit('login', {user: name, avatar: email, id: id});
				}

			});
		}

		else if(data.number === 1) {

			showMessage("personinchat",data);

			loginForm.on('submit', function(e){

				e.preventDefault();

				name = $.trim(hisName.val());

				if(name.length < 1){
					alert("Please enter a nick name longer than 1 character!");
					return;
				}

				if(name == data.user){
					alert("There already is a \"" + name + "\" in this room!");
					return;
				}
				email = hisEmail.val();

				if(!isValid(email)){
					alert("Wrong e-mail format!");
				}
				else{

					socket.emit('login', {user: name, avatar: email, id: id});
				}

			});
		}

		else {
			showMessage("tooManyPeople");
		}

	});
  • If-else on data.number == 0/1 
  • 0 = Showmsg
  • 1 = showmsg 

chat.js - toomany & Createmsg

 	socket.on('tooMany', function(data){

		if(data.boolean && name.length === 0) {

			showMessage('tooManyPeople');
		}
	});

 function createChatMessage(msg,user,imgg,now){

		var who = '';

		if(user===name) {
			who = 'me';
		}
		else {
			who = 'you';
		}
		chats.append(li);

		messageTimeSent = $(".timesent");
		messageTimeSent.last().text(now.fromNow());
	}

Chat.js - showmessage

 function showMessage(status,data){

		if(status === "connected"){
		}

		else if(status === "inviteSomebody"){
		}

		else if(status === "personinchat"){
		}

		else if(status === "youStartedChatWithNoMessages") {
			});
		}

		else if(status === "heStartedChatWithNoMessages") {
			});
		}

		else if(status === "chatStarted"){
		}

		else if(status === "somebodyLeft"){
		}

		else if(status === "tooManyPeople") {
		}
	}
  • Simple if-else

Join us on meetup 




Thanks !


Building Realtime Apps using Node/Socket.IO, Mongo and Angular

By Gautam Anand

Building Realtime Apps using Node/Socket.IO, Mongo and Angular

Singapore Node.js Community Meetup#2

  • 13,853