IN This tutorial we’ll Create a Real-time Chatting App Using Angularjs & Socket.io . Tutorial is divided in Three chapter . In First Chapter We’ll learn Basics of socket.io , we’ll also configure Socket.io server for our chatting app . In Second Chapter We’ll be coding backend Of Our Application And In last Chapter we’ll add routes & views To our app . ok Lets get started …
Some Info About socket.io
Created in 2010 by JavaScript developer, Guillermo Rauch, Socket.io aimed to abstract Node.js’ real-time application development. Since then, it has evolved dramatically, released in nine major versions before being broken in its latest version into two different modules: Engine.io and Socket.io. The idea behind Engine.io was to create a more stable real-time module, which first opens a long-polling XHR communication and then tries to upgrade the connection to a WebSockets
channel. The new version of Socket.io uses the Engine.io module and provides the developer with various features such as events, rooms, and automatic connection recovery, which you would otherwise implement by yourself.
Installing Socket.io
Before you can use the Socket.io module, you will need to install it using npm
. To do so, change your package.json
file as follows:
{
"name": "MEAN",
"version": "0.0.9",
"dependencies": {
"express": "~4.8.8",
"morgan": "~1.3.0",
"compression": "~1.0.11",
"body-parser": "~1.8.0",
"method-override": "~2.2.0",
"express-session": "~1.7.6",
"ejs": "~1.0.0",
"connect-flash": "~0.1.1",
"mongoose": "~3.8.15",
"passport": "~0.2.1",
"passport-local": "~1.0.0",
"passport-facebook": "~1.0.3",
"passport-twitter": "~1.0.2",
"passport-google-oauth": "~0.1.5",
"socket.io": "~1.1.0"
}
}
To install the Socket.io module, go to your application’s root folder and issue the following command in your command-line tool:
$ npm install
As usual, this will install the specified version of Socket.io in your node_modules
folder. When the installation process is successfully over, your will need be to configure your Express application to work in conjunction with the Socket.io module and start your socket server.
Configuring the Socket.io server
After you’ve installed the Socket.io module, you will need to start the socket server in conjunction with the Express application. For this, you will have to make the following changes in your config/express.js
file:
var config = require('./config'), http = require('http'), socketio = require('socket.io'), express = require('express'), morgan = require('morgan'), compress = require('compression'), bodyParser = require('body-parser'), methodOverride = require('method-override'), session = require('express-session'), flash = require('connect-flash'), passport = require('passport'); module.exports = function() { var app = express(); var server = http.createServer(app); var io = socketio.listen(server); if (process.env.NODE_ENV === 'development') { app.use(morgan('dev')); } else if (process.env.NODE_ENV === 'production') { app.use(compress()); } app.use(bodyParser.urlencoded({ extended: true })); app.use(bodyParser.json()); app.use(methodOverride()); app.use(session({ saveUninitialized: true, resave: true, secret: config.sessionSecret })); app.set('views', './app/views'); app.set('view engine', 'ejs'); app.use(flash()); app.use(passport.initialize()); app.use(passport.session()); require('../app/routes/index.server.routes.js')(app); require('../app/routes/users.server.routes.js')(app); require('../app/routes/articles.server.routes.js')(app); app.use(express.static('./public')); return server; };
Let’s go over the changes you made to your Express configuration. After including the new dependencies, you used the http
core module to create a server
object that wraps your Express app
object. You then used the socket.io
module and its listen()
method to attach the Socket.io server with your server
object. Finally, you returned the new server
object instead of the Express application object. When the server starts, it will run your Socket.io server along with your Express application.
While you can already start using Socket.io, there is still one major problem with this implementation. Since Socket.io is a standalone module, requests that are sent to it are detached from the Express application. This means that the Express session information is not available in a socket connection. This raises a serious obstacle when dealing with your Passport authentication in the socket layer of your application. To solve this issue, you will need to configure a persistent session storage, which will allow you to share your session information between the Express application and Socket.io handshake requests.
Configuring the Socket.io session
To configure your Socket.io session to work in conjunction with your Express sessions, you have to find a way to share session information between Socket.io and Express. Since the Express session information is currently being stored in memory, Socket.io will not be able to access it properly. So, a better solution would be to store the session information in your MongoDB. Fortunately, there is node module named connect-mongo
that allows you to store session information in a MongoDB instance almost seamlessly. To retrieve the Express session information, you will need some way to parse the signed session cookie information. For this purpose, you’ll also install the cookie-parser
module, which is used to parse the cookie header and populate the HTTP request object with cookies-related properties.
Installing the connect-mongo and cookie-parser modules
Before you can use the connect-mongo
and cookie-parser
modules, you will need to install it using npm
. To do so, change your package.json
file as follows:
{ "name": "MEAN", "version": "0.0.9", "dependencies": { "express": "~4.8.8", "morgan": "~1.3.0", "compression": "~1.0.11", "body-parser": "~1.8.0", "method-override": "~2.2.0", "express-session": "~1.7.6", "ejs": "~1.0.0", "connect-flash": "~0.1.1", "mongoose": "~3.8.15", "passport": "~0.2.1", "passport-local": "~1.0.0", "passport-facebook": "~1.0.3", "passport-twitter": "~1.0.2", "passport-google-oauth": "~0.1.5", "socket.io": "~1.1.0", "connect-mongo": "~0.4.1", "cookie-parser": "~1.3.3" } }
To install the new modules, go to your application’s root folder and issue the following command in your command-line tool:
$ npm install
As usual, this will install the specified versions of the connect-mongo
and cookie-parser
modules in your node_modules
folder. When the installation process is successfully over, your next step will be to configure your Express application to use connect-mongo
as session storage.
Configuring the connect-mongo module
To configure your Express application to store session information using the connect-mongo
module, you will have to make a few changes. First, you will need to change your config/express.js
file as follows:
var config = require('./config'), http = require('http'), socketio = require('socket.io'), express = require('express'), morgan = require('morgan'), compress = require('compression'), bodyParser = require('body-parser'), methodOverride = require('method-override'), session = require('express-session'), MongoStore = require('connect-mongo')(session), flash = require('connect-flash'), passport = require('passport'); module.exports = function(db) { var app = express(); var server = http.createServer(app); var io = socketio.listen(server); if (process.env.NODE_ENV === 'development') { app.use(morgan('dev')); } else if (process.env.NODE_ENV === 'production') { app.use(compress()); } app.use(bodyParser.urlencoded({ extended: true })); app.use(bodyParser.json()); app.use(methodOverride()); var mongoStore = new MongoStore({ db: db.connection.db }); app.use(session({ saveUninitialized: true, resave: true, secret: config.sessionSecret, store: mongoStore })); app.set('views', './app/views'); app.set('view engine', 'ejs'); app.use(flash()); app.use(passport.initialize()); app.use(passport.session()); require('../app/routes/index.server.routes.js')(app); require('../app/routes/users.server.routes.js')(app); require('../app/routes/articles.server.routes.js')(app); app.use(express.static('./public')); return server; };
In the preceding code snippet, you configured a few things. First, you loaded the connect-mongo
module, and then passed the Express session module to it. Then, you created a new connect-mongo
instance and passed it your Mongoose connection object. Finally, you used the Express session store option to let the Express session module know where to store the session information.
As you can see, your Express configuration method requires a db
argument. This argument is the Mongoose connection object, which will be passed to the Express configuration method from the server.js
file when it requires the express.js
file. So, go to your server.js
file and change it as follows:
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
var mongoose = require('./config/mongoose'),
express = require('./config/express'),
passport = require('./config/passport');
var db = mongoose();
var app = express(db);
var passport = passport();
app.listen(3000);
module.exports = app;
console.log('Server running at http://localhost:3000/');
Once the Mongoose connection is created, the server.js
file will call the express.js
module method and pass the Mongoose database property to it. In this way, Express will persistently store the session information in your MongoDB database so that it will be available for the Socket.io session. Next, you will need to configure your Socket.io handshake middleware to use the connect-mongo
module and retrieve the Express session information.
Configuring the Socket.io session
To configure the Socket.io session, you’ll need to use the Socket.io configuration middleware and retrieve your session user. Begin by creating a new file named socketio.js
in your config
folder to store all your Socket.io-related configurations. In your new file, add the following lines of code:
var config = require('./config'), cookieParser = require('cookie-parser'), passport = require('passport'); module.exports = function(server, io, mongoStore) { io.use(function(socket, next) { cookieParser(config.sessionSecret)(socket.request, {}, function(err) { var sessionId = socket.request.signedCookies['connect.sid']; mongoStore.get(sessionId, function(err, session) { socket.request.session = session; passport.initialize()(socket.request, {}, function() { passport.session()(socket.request, {}, function() { if (socket.request.user) { next(null, true); } else { next(new Error('User is not authenticated'), false); } }) }); }); }); }); io.on('connection', function(socket) { /* ... */ }); };
Let’s go over the new Socket.io configuration file. First, you required the necessary dependencies, and then you used the io.use()
configuration method to intercept the handshake process. In your configuration function, you used the Express cookie-parser
module to parse the handshake request cookie and retrieve the Express sessionId
. Then, you used the connect-mongo
instance to retrieve the session information from the MongoDB storage. Once you retrieved the session object, you used the passport.initialize()
and passport.session()
middleware to populate the session’s user
object according to the session information. If a user is authenticated, the handshake middleware will call the next()
callback and continue with the socket initialization; otherwise, it will use the next()
callback in a way that informs Socket.io that a socket connection cannot be opened. This means that only authenticated users can open a socket communication with the server and prevent unauthorized connections to your Socket.io server.
To complete your Socket.io server configuration, you will need to call the Socket.io configuration module from your express.js
file. Go to your config/express.js
file and paste the following line of code right before you return the server
object:
require('./socketio')(server, io, mongoStore);
This will execute your Socket.io configuration method and will take care of setting the Socket.io session. Now that you have everything configured, let’s see how you can use Socket.io and MEAN to easily build a simple chat.
Building a Socket.io chat
Lets Build a chatting application which will be constructed from several server event handlers, but most of the implementation will take place in your AngularJS application. We’ll begin with setting the server event handlers.
Setting the event handlers of the chat server
Before implementing the chat client in your AngularJS application, you’ll first need to create a few server event handlers. You already have a proper application structure, so you won’t implement the event handlers directly in your configuration file. Instead, it would be better to implement your chat logic by creating a new file named chat.server.controller.js
inside your app/controllers
folder. In your new file, paste the following lines of code:
module.exports = function(io, socket) { io.emit('chatMessage', { type: 'status', text: 'connected', created: Date.now(), username: socket.request.user.username }); socket.on('chatMessage', function(message) { message.type = 'message'; message.created = Date.now(); message.username = socket.request.user.username; io.emit('chatMessage', message); }); socket.on('disconnect', function() { io.emit('chatMessage', { type: 'status', text: 'disconnected', created: Date.now(), username: socket.request.user.username }); }); };
In this file, you implemented a couple of things. First, you used the io.emit()
method to inform all the connected socket clients about the newly connected user. This was done by emitting the chatMessage
event, and passing a chat message object with the user information and the message text, time, and type. Since you took care of handling the user authentication in your socket server configuration, the user information is available from the socket.request.user
object.
Next, you implemented the chatMessage
event handler that will take care of messages sent from the socket client. The event handler will add the message type, time, and user information, and it will send the modified message object to all connected socket clients using the io.emit()
method.
Our last event handler will take care of handling the disconnect
system event. When a certain user is disconnected from the server, the event handler will notify all the connected socket clients about this event by using the io.emit()
method. This will allow the chat view to present the disconnection information to other users.
You now have your server handlers implemented, but how will you configure the socket server to include these handlers? To do so, you will need to go back to your config/socketio.js
file and slightly modify it:
var config = require('./config'),
cookieParser = require('cookie-parser'),
passport = require('passport');
module.exports = function(server, io, mongoStore) {
io.use(function(socket, next) {
cookieParser(config.sessionSecret)(socket.request, {}, function(err) {
var sessionId = socket.request.signedCookies['connect.sid'];
mongoStore.get(sessionId, function(err, session) {
socket.request.session = session;
passport.initialize()(socket.request, {}, function() {
passport.session()(socket.request, {}, function() {
if (socket.request.user) {
next(null, true);
} else {
next(new Error('User is not authenticated'), false);
}
})
});
});
});
});
io.on('connection', function(socket) {
require('../app/controllers/chat.server.controller')(io, socket);
});
};
Notice how the socket server connection
event is used to load the chat controller. This will allow you to bind your event handlers directly with the connected socket.
Congratulations, you’ve successfully completed your server implementation! Next, you’ll see how easy it is to implement the AngularJS chat functionality. Let’s begin with the AngularJS service.
Creating the Socket service
The provided Socket.io client method is used to open a connection with the socket server and return a client instance that will be used to communicate with the server. Since it is not recommended to use global JavaScript objects, you can leverage the services singleton architecture and wrap your socket client.
Let’s begin by creating the public/chat
module folder. Then, create the public/chat/chat.client.module.js
initialization file with the following line of code:
angular.module('chat', []);
Now, proceed to create a public/chat/services
folder for your socket service. In the public/chat/services
folder, create a new file named socket.client.service.js
that contains the following code snippet:
angular.module('chat').service('Socket', ['Authentication', '$location', '$timeout', function(Authentication, $location, $timeout) { if (Authentication.user) { this.socket = io(); } else { $location.path('/'); } this.on = function(eventName, callback) { if (this.socket) { this.socket.on(eventName, function(data) { $timeout(function() { callback(data); }); }); } }; this.emit = function(eventName, data) { if (this.socket) { this.socket.emit(eventName, data); } }; this.removeListener = function(eventName) { if (this.socket) { this.socket.removeListener(eventName); } }; } ]);
Let’s review this code for a moment. After injecting the services, you checked whether the user is authenticated using the Authentication
service. If the user is not authenticated, you redirected the request back to the home page using the $location
service. Since AngularJS services are lazily loaded, the Socket service will only load when requested. This will prevent unauthenticated users from using the Socket service. If the user is authenticated, the service socket
property is set by calling the io()
method of Socket.io.
Next, you wrapped the socket emit()
, on()
, and removeListenter()
methods with compatible service methods. It is worth checking the service on()
method. In this method, you used a common AngularJS trick that involves the $timeout
service. The problem we need to solve here is that AngularJS data binding only works for methods that are executed inside the framework. This means that unless you notify the AngularJS compiler about third-party events, it will not know about changes they cause in the data model. In our case, the socket client is a third-party library that we integrate in a service, so any events coming from the socket client might not initiate a binding process. To solve this problem, you can use the $apply
and $digest
methods; however, this often causes an error, since a digest cycle might already be in progress. A cleaner solution is to use $timeout
trick. The $timeout
service is a wrapper around the window.setTimeout()
method, so calling it without the timeout
argument will basically take care of the binding issue without any impact on user experience
Once you have the Socket service ready, all you have to do is implement the chat controller and chat view. Let’s begin by defining the chat controller.
Creating the chat controller
The chat controller is where you implement your AngularJS chat functionality. To implement your chat controller, you’ll first need to create a public/chat/controllers
folder. In this folder, create a new file named chat.client.controller.js
that contains the following code snippet:
angular.module('chat').controller('ChatController', ['$scope', 'Socket', function($scope, Socket) { $scope.messages = []; Socket.on('chatMessage', function(message) { $scope.messages.push(message); }); $scope.sendMessage = function() { var message = { text: this.messageText, }; Socket.emit('chatMessage', message); this.messageText = ''; } $scope.$on('$destroy', function() { Socket.removeListener('chatMessage'); }) } ]);
In the controller, you first created a messages array and then implemented the chatMessage
event listener that will add retrieved messages to this array. Next, you created a sendMessage()
method that will send new messages by emitting the chatMessage
event to the socket server. Finally, you used the in-built $destroy
event to remove the chatMessage
event listener from the socket client. The $destory
event will be emitted when the controller instance is deconstructed. This is important because the event handler will still get executed unless you remove it.
Creating the chat view
The chat view will be constructed from a simple form and a list of chat messages. To implement your chat view, you’ll first need to create a public/chat/views
folder. In this folder, create a new file named chat.client.view.html
that contains the following code snippet:
<section data-ng-controller="ChatController"> <div data-ng-repeat="message in messages" data-ng-switch="message.type"> <strong data-ng-switch-when='status'> <span data-ng-bind="message.created | date:'mediumTime'"></span> <span data-ng-bind="message.username"></span> <span>is</span> <span data-ng-bind="message.text"></span> </strong> <span data-ng-switch-default> <span data-ng-bind="message.created | date:'mediumTime'"></span> <span data-ng-bind="message.username"></span> <span>:</span> <span data-ng-bind="message.text"></span> </span> </div> <form ng-submit="sendMessage();"> <input type="text" data-ng-model="messageText"> <input type="submit"> </form> </section>
In this view, you used the ng-repeat
directive to render the messages list and the ng-switch
directive to distinguish between status messages and regular messages. You also used the AngularJS date
filter to properly present the message time. Finally, you finished the view with a simple form that uses the ng-submit
directive to invoke the sendMessage()
method. Next, you will need to add a chat route to present this view.
Adding chat routes
To present the view, you will need to add a new route for it. To do so, first create the public/chat/config
folder. In this folder, create a new file named chat.client.routes.js
that contains the following code snippet:
angular.module('chat').config(['$routeProvider', function($routeProvider) { $routeProvider. when('/chat', { templateUrl: 'chat/views/chat.client.view.html' }); } ]);
This should already be a familiar pattern, so let’s proceed to finalize the chat implementation.
Finalizing the chat implementation
To finalize your chat implementation, you will need to make a few changes in your main application page and include the Socket.io client file and your new chat files. Go to the app/views/index.ejs
file and make the following changes:
<!DOCTYPE html> <html xmlns:ng="http://angularjs.org"> <head> <title><%= title %></title> </head> <body> <section ng-view></section> <script type="text/javascript"> window.user = <%- user || 'null' %>; </script> <script type="text/javascript" src="/socket.io/socket.io.js"></script> <script type="text/javascript" src="/lib/angular/angular.js"></script> <script type="text/javascript" src="/lib/angular-route/angular-route.js"></script> <script type="text/javascript" src="/lib/angular-resource/angular-resource.js"></script> <script type="text/javascript" src="/articles/articles.client.module.js"></script> <script type="text/javascript" src="/articles/controllers/articles.client.controller.js"></script> <script type="text/javascript" src="/articles/services/articles.client.service.js"></script> <script type="text/javascript" src="/articles/config/articles.client.routes.js"></script> <script type="text/javascript" src="/example/example.client.module.js"></script> <script type="text/javascript" src="/example/controllers/example.client.controller.js"></script> <script type="text/javascript" src="/example/config/example.client.routes.js"></script> <script type="text/javascript" src="/users/users.client.module.js"></script> <script type="text/javascript" src="/users/services/authentication.client.service.js"></script> <script type="text/javascript" src="/chat/chat.client.module.js"></script> <script type="text/javascript" src="/chat/services/socket.client.service.js"></script> <script type="text/javascript" src="/chat/controllers/chat.client.controller.js"></script> <script type="text/javascript" src="/chat/config/chat.client.routes.js"></script> <script type="text/javascript" src="/application.js"></script> </body> </html>
Notice how we first added the Socket.io file. It’s always a good practice to include third-party libraries before your application files. Now, you’ll need to change the public/application.js
file to include your new chat
module:
var mainApplicationModuleName = 'mean';
var mainApplicationModule = angular.module(mainApplicationModuleName, ['ngResource', 'ngRoute', 'users', 'example', 'articles', 'chat']);
mainApplicationModule.config(['$locationProvider',
function($locationProvider) {
$locationProvider.hashPrefix('!');
}
]);
if (window.location.hash === '#_=_') window.location.hash = '#!';
angular.element(document).ready(function() {
angular.bootstrap(document, [mainApplicationModuleName]);
});
To finish up your chat implementation, change your public/example/views/example.client.view.html
file and add a new chat link:
<section ng-controller="ExampleController">
<div data-ng-show="!authentication.user">
<a href="/signup">Signup</a>
<a href="/signin">Signin</a>
</div>
<div data-ng-show="authentication.user">
<h1>Hello <span data-ng-bind="authentication.user.fullName"></span></h1>
<a href="/signout">Signout</a>
<ul>
<li><a href="/#!/chat">Chat</a></li>
<li><a href="/#!/articles">List Articles</a></li>
<li><a href="/#!/articles/create">Create Article</a></li>
</ul>
</div>
</section>
Once you are finished with these changes, your new chat example should be ready to use. Use your command-line tool and navigate to the MEAN application’s root folder. Then, run your application by typing the following command:
$ node server
Once your application is running, open two different browsers and sign up with two different users. Then, navigate to http://localhost:3000/#!/chat
and try sending chat messages between your two clients. You’ll be able to see how chat messages are being updated in real time. Your MEAN application now supports real-time communication.