Day 4 - How to build your own team chat in five days - ExpressJS, Socket.io and AngularJS component-based design patterns

16 April, 2015

This is part 4 of an ongoing series of blog posts. Read part 1 first and then come back, please!


Welcome back to day 4 of "How to build your own team chat in five days"! I haven't had the time to introduce you properly to the first "real" iteration of "chitchat", yesterday.

First of all, the complete sourcecode can be found in the github repository.

And here's again the directory structure:

├── public
│ ├── app
│ ├── images
│ ├── jspm_packages
│ ├── config.js
│ ├── styles
│ └── index.html
└── styles
├── base
├── layout
├── modules
└── application.scss
├── node_modules
├── package.json
├── gulpfile.js
├── app.js
└── README.md

Let's dive into the sourcecode starting with the ExpressJS part.

ExpressJS server side #

If you already have experience in ExpressJS the following configuration and setup code in app.js should not surprise you:

var express        = require('express');
var bodyParser = require('body-parser');
var logger = require('morgan');
var methodOverride = require('method-override');
var multer = require('multer');
var path = require('path');

var app = express();
var http = require('http').createServer(app);

app.set('port', process.env.PORT || 3000);

app.use(logger('dev'));
app.use(methodOverride());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(multer());
app.use(express.static(path.join(__dirname, 'public')));

// development only
if ('development' == app.get('env')) {
app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
}

// some code omitted here

http.listen(app.get('port'), function() {
console.log('Express server listening on port ' + app.get('port'));
});

This is a default setup for an ExpressJS server which serves static files from public/. Additionally, I've required the modules body-parser and method-override in order to handle JSON requests. So, this is a good basis to implement some routes for a JSON REST API or similar.

But, since we are going to look into the real-time part of messaging today, let's see how to integrate Socket.io here first.

A simple chat using Socket.io #

In order to make Socket.io work with ExpressJS we first have to initialize it:

// first the setup of ExpressJS itself
var app = express();
var http = require('http').createServer(app);

// now socket.io
var io = require('socket.io').listen(http);

// and then we can start the server
http.listen(app.get('port'), function() {
console.log('Express server listening on port ' + app.get('port'));
});

If you now start your server with node app.js, or npm start since that's configured in package.json, you are now ready to initiate a websocket connection.

The Socket.io server goes one step further and also delivers the client library to integrate with. You can actually request it via the following URL localhost:3000/socket.io/socket.io.js and you can find me do the same in the public/index.html head via:

<script src="/socket.io/socket.io.js"></script>

After all the setup work is done we can finally initiate a Websocket connection with our client. I've put all the Socket.io code into public/app/web_socket.js, let's have a look together:

export default class WebSocket {

constructor() {
this.init();
}

init() {
let host = window.location.origin;
console.log("WEBSOCKET connecting to", host);

this.socket = io.connect(host);

this.socket.on('connect', () => {
let sessionId = this.socket.io.engine.id;

console.log("WEBSOCKET connected with session id", sessionId);

this.socket.emit('new_user', { id: sessionId });
})
}
}

WebSocket.$inject = [];

First we need the url to connect to via Websocket and window.location.origin returns http://localhost:3000 for us. Perfect!

Next is the actual connect via io.connect(host) and then we implement our first callback. Socket.io emits a connect event when the connection handshake was successful. And we use it and retrieve our sessionID from it. That is basically as good as it gets for our user in the current prototype.

The WebSocket class calls init() in the constructor in order to connect when initializing the class and I'm making sure there's only a since instance by using it as an Angular service.

That's why in main.js it is integrated in the following way:

import WebSocket from './web_socket';

angular.module('mainApp', [
// dependencies omitted
]).service(WebSocket.name, WebSocket);

One last thing here to mention is that I emit a new_user event with the sessionID since I want to notify all interested parties that a new participant joined the chat.

Let's go back to app.js on the server side to see how the connection handshake and new_user event is handled:

// [
// { session_id: "5W5f-HSzolBOjMj7AAAC", name: "Peter" },
// { session_id: "YXlbm_LmHD7oUGwkAAAD", name: "Martin"}
// ]
var participants = [];
var nameCounter = 1;

io.on("connection", function(socket) {

socket.on("new_user", function(data) {

var newName = "Guest " + nameCounter++;
participants.push({ id: data.id, name: newName });

io.sockets.emit("new_connection", {
user: {
id: data.id,
name: newName
},
sender:"system",
created_at: new Date().toISOString(),
participants: participants
});
});
});

First of all we have the connection event I'm listening here, which gets triggered whenever a new client is connected to the server. Then the server registered for the new_user event which we saw on the client-side above. And I basically create a dumb Guest <number> user name, add it to the array of participants alongside the user id and then emit another event new_connection which is send to inform all parties that the participants changed.

Wouldn't it be cool if we would have some kind of currentUser on the client? Note that the new_connection payload also contains the user id and name alongside the participants array. We can use this event on the client side to set our current user including all the information (in our case name only) we gathered on the server.

So, let's go back to web_socket.js on the client:

import Auth from "./auth";

export default class WebSocket {

constructor($rootScope) {
this.$rootScope = $rootScope;
this.init();
}

init() {
// code omitted

this.socket.on('connect', () => {
let sessionId = this.socket.io.engine.id;

// code ommited

// this is the new event handler
this.socket.on('new_connection', (data) => {

if (data.user.id === sessionId) {
this.$rootScope.$apply(() => {
Auth.setCurrentUser(data.user);
});
}
});
});
}
}

WebSocket.$inject = ['$rootScope'];

I've changed a few things but let's first focus on the new_connection event handler. It compares the user id which was send from the server with the sessionId and if they match we set the current user using the Auth class.

The Auth class is pretty simple:

export default class Auth {

static setCurrentUser(user) {
this.currentUser = user;
}

static getCurrentUser() {
return this.currentUser;
}
}

It contains a function for setting and getting the current user, but note that the methods are both static so we can access them using Auth.getCurrentUser() instead of creating an instance of Auth. I've could have used another Angular service in this case but I'd like to look into native ES6 ways of structuring my code first.

The attentive reader has already spotted one Angular-specific part in our WebSocket class. I'm wrapping the Auth.setCurrentUser call in a $rootScope.$apply to let Angular know that I've changed something which it might have access to on the scope. For example when rendering the current user name in our HTML document the change would not get reflected without this $apply call. BTW, this is a problem which will be gone in AngularJS 2.0 and I'm very much looking forward to it!

There's one more function in our WebSocket class I'd like you to check out:

export default class WebSocket {

// details omitted here

on(key, callback) {
this.socket.on(key, (data) => {
this.$rootScope.$apply(() => {
callback(data)
});
});
}
}

This on function let's other components in my code base register for events emitted by the server. It will come in handy when rendering the list of participants for example. Which brings me to the AngularJS side of the prototype.

AngularJS using a component-oriented approach #

Let's starts with the directory structure of the AngularJS app inside the public/ directory:

├── app
│ ├── message_form
│ ├── message_list
│ ├── participants_list
│ ├── sidebar
│ ├── auth.js
│ ├── bootstrap.js
│ ├── main.js
│ ├── vendor.js
│ └── web_socket.js
├── jspm_packages
├── styles
├── images
├── config.js
├── index.html

You already know main.js, bootstrap.js and most other files from the previous days. And we've already talked about auth.js and web_socket.js. So, let's focus now on the way I've structured the AngularJS components in index.html:

<body>
<sidebar></sidebar>

<div class="main-content">

<div class="main-header">
<div class="left">
<h2># general</h2>
</div>
<div class="right">
<input class="search" type="search" placeholder="Search...">
</div>
</div>

<message-list></message-list>
<message-form></message-form>
</div>
</body>

You can see that I've introduced new HTML elements for the sidebar, message-list and message-form using Angular directives. And some of these components contain again child components. This can be easily visualized too:

Components and nested components

That way the structure of my code follows very closely the actual visual structure of the dom elements. Here's the directory structure of these components:

├── message_form
│ ├── message_form.controller.js
│ ├── message_form.directive.js
│ ├── message_form.html
│ ├── message_form.module.js
│ └── submit_form_on_return.directive.js
├── message_list
│ ├── message_list.controller.js
│ ├── message_list.directive.js
│ ├── message_list.html
│ └── message_list.module.js
├── participants_list
│ ├── participants_list.controller.js
│ ├── participants_list.directive.js
│ ├── participants_list.html
│ └── participants_list.module.js
├── sidebar
│ ├── sidebar.controller.js
│ ├── sidebar.directive.js
│ ├── sidebar.html
│ └── sidebar.module.js
├── auth.js
├── bootstrap.js
├── main.js
├── vendor.js
└── web_socket.js

The all look pretty much the same. I use a directory for each component and each component contains an Angular module and a directive which itself encapsulates the controller and the template.

Let's have a look at some of these components next, starting with the message-form component.

Message Form Component #

The message form component is responsible for rendering a textarea where the user can post new messages. Additionally, it also handles the server communication to create the message on the server.

Message form component

We start again with the visual aspects of the message_form.html:


<div class="message-form">
<form ng-submit="ctrl.sendMessage(ctrl.message)" submit-form-on-return>
<textarea ng-model="ctrl.message" placeholder="Press enter to send message" autofocus/>
</form>
</div>

It contains a form with an ng-submit directive and a textarea with an ng-model. Nothing fancy here, except that I used a what I call a behaviour directive in comparison to the component directives which you have seen visualized above already. The directive submits the form when pressing the enter or return button on your keyboard.

Now you might have noticed that I use ctrl in the ng-submit and ng-model directives, as for example ctrl.message. So, let's have a look at the directive implementation and more specifically how the scope is set up:

import template from "./message_form.html!text";
import MessageFormController from "./message_form.controller";

function messageFormDirective() {
return {
restrict: "E",
replace: true,
scope: {},
template: template,
bindToController: true,
controllerAs: "ctrl",
controller: MessageFormController
};
}

messageFormDirective.$inject = [];

export default messageFormDirective;

Focusing on the scope specific parts here, I've started with creating an isolated scope with scope and the scope is bound to the controller using bindToController as ctrl using the controllerAs property.

Now all these properties are way to complicated and there's a lot of duplication in my code, but with Angular 1.4 (Angular bindToController git commit) there's most probably going to be a new syntax bindToController which combines all these properties into a single property - perfect for my component directives! And another nice step towards an easy migration path to Angular 2.0!

Using the ctrl inside the template makes sense but of course in the controller itself we just use the controller's this syntax:

import Auth from "../auth";

class MessageFormController {

constructor($http) {
this.$http = $http;
}

sendMessage(message) {
let params = {
message: message,
created_at: new Date().toISOString(),
user_id: Auth.getCurrentUser().id
};

this.$http.post("/messages", params).then(
() => {
this.message = "";
},
(reason) => {
console.log("error", reason);
}
);
}

}

MessageFormController.$inject = ["$http"];

export default MessageFormController;

The sendMessage function is what's called in the template and is responsible for doing an AJAX POST request to the /messages route. The JSON payload (default format in Angular's $http service) contains the actual message, the current user id and the current date.

On the server side in app.js I've defined a route for this messages/ endpoint:

app.post("/messages", function(request, response) {
var message = request.body.message;

if(message && message.trim().length > 0) {
var user_id = request.body.user_id;
var created_at = request.body.created_at;
var user = _.findWhere(participants, { id: user_id });

// let our chatroom know there was a new message
io.sockets.emit("incoming_message", { message: message, user: user, created_at: created_at });

response.json(200, { message: "Message received" });
} else {
return response.json(400, { error: "Invalid message" });
}
})

After checking if the request payload contains a message we extract the user and the created_at and emit an event incoming_message to let all other participants receive this message via Socket.io. Now, the logical next step here would be to add a persistence layer and actually store the message, but not for this current state of the prototype.

We want to focus next on the message list component which listens for this incoming_message event.

Message list component #

The message list component renders a list of messages and handles the incoming_message event.

Message list component

Let's start again with the template:


<div class="message-list">
<div ng-repeat="messageContent in ctrl.messages" class="message-item {{messageContent.type}}">
<img src="images/profile.jpg" class="avatar" ng-if="messageContent.type == 'message'">
<div class="message-container">
<div class="meta">
<span class="author">{{messageContent.user.name}}</span> <span class="date">{{messageContent.created_at}}</span>
</div>
<div class="message">
{{messageContent.message}}
</div>
</div>
</div>
</div>

The message list uses the ng-repeat directive to render a list of messaging. Note, that I've introduced two types of messages. The type message is used for a user generated message, the type notification for a system generated message, for example: "User Guest 1 joined", etc. Nothing fancy here, let's skip the setup of the directive too, because it's so similar to the other directives and focus instead on the controller:

class MessageListController {

constructor(WebSocket) {
this.messages = [];

this.WebSocket = WebSocket;

this.register();
}

register() {
this.WebSocket.on('incoming_message', (data) => {
this.handleIncomingMessage(data);
});

this.WebSocket.on('new_connection', (data) => {
this.handleNewConnection(data);
});

this.WebSocket.on('user_disconnected', (data) => {
this.handleUserDisconnected(data);
});
}

handleIncomingMessage(data) {
this.messages.push({ message: data.message, user: data.user, created_at: data.created_at, type: "message" });
}

handleUserDisconnected(data) {
this.messages.push({ message: `User ${data.user.name} disconnected`, name: "System", created_at: data.created_at, type: "notification" });
}

handleNewConnection(data) {
this.messages.push({ message: `User ${data.user.name} joined`, name: "System", created_at: data.created_at, type: "notification" });
}

}

MessageListController.$inject = ["WebSocket"];

export default MessageListController;

The controller is starting with an empty array of messages and registers a whole lot of listeners for various events. The incoming_message event we discussed above already will simply add the user generated message to messages array. The other events new_connection and user_disconned will add system notification to the message list.

Now one could argue why this complexity on the client side and in fact I might decide for a future iteration to move all this to the server which then handles the various events and emits a single new_message. We'll see how this works out for this week!


There are two more components namely the sidebar and the participants-list, but they are in fact pretty similar to what you've seen here already so I'm not going to go into further explaining them here.

That's it for today! I'm going to get back to coding. So, stay tuned for tomorrow where I hopefully have the time to add some more polish to the frontend!


The complete sourcecode can be found in the github repository.


This is part 4 of an ongoing series of blog posts. Continue reading part 5.