Building a Chat Web Application

It's now time to move onto building a proper web application. Rather than the simple todo application you would build in other systems, for node the standard is a chat application (which is a lot more exciting!).

To do this, we will use Primus and ws both by Arnout Kazemier.

Primus is a wrapper library over many different WebSockets libraries. WebSockets is a technology that allows browser clients to communicate with servers. Until WebSockets became a thing in 2011, such functionality was achieved via several dodgy workarounds.

We can install Primus for our project like so:

npm init # setup our project
npm install --save express # install express, which we will attach Primus to
npm install --save primus # install Primus
npm install --save ws # the WebSockets library we will sue with Primus

Server to Browser Relay

The most basic example of this is the following, which will allow the server to broadcast to all clients, while receiving responses from clients:

socket-server.js
socket-client.html
'use strict'
// Requires
const express = require('express')
const Primus = require('primus')
const pathUtil = require('path')
// Application
const app = require('express')()
const server = require('http').createServer(app)
const primus = new Primus(server, { transformer: 'websockets' })
// Middlewares
app.get('/', function (req, res) {
require('fs').createReadStream(pathUtil.join(__dirname, 'socket-client.html')).pipe(res)
})
app.use(function (req, res) {
res.status(404).send('404 Not Found. 🙁 \n')
})
// Socket
primus.on('connection', function (spark) {
console.log('connection has the following headers', spark.headers)
console.log('connection was made from', spark.address)
console.log('connection id', spark.id)
// Receive messages
spark.on('data', function (message) {
console.log('connection', spark.id, 'sends', message.toString())
})
// Send messages
process.stdin.on('data', function (message) {
spark.write('The server has spoken: ' + message.toString())
})
// Send an initial hello
spark.write('Hello user. I am the server communicating to you.')
})
// Listen
server.listen(8080)

Test it: http://localhost:8080/

Browser to Browser Broadcasting

A more useful example of this, is your basic chat app, which allows clients to broadcast to other clients:

chat-server.js
chat-client.html
'use strict'
// Requires
const express = require('express')
const Primus = require('primus')
const pathUtil = require('path')
// Application
const app = require('express')()
const server = require('http').createServer(app)
const primus = new Primus(server, { transformer: 'websockets' })
// Middlewares
app.get('/', function (req, res) {
require('fs').createReadStream(pathUtil.join(__dirname, 'chat-client.html')).pipe(res)
})
app.use(function (req, res) {
res.status(404).send('404 Not Found. 🙁 \n')
})
// Socket
primus.on('connection', function (spark) {
console.log('connection has the following headers', spark.headers)
console.log('connection was made from', spark.address)
console.log('connection id', spark.id)
// Receive messages
spark.on('data', function (message) {
// Broadcast them back to everyone
primus.write('user ' + spark.id + ' sends ' + message.toString())
})
// Send an initial hello
spark.write('Hello user ' + spark.id + '. I am the server communicating to you.')
})
// Listen
server.listen(8080)

Test it: http://localhost:8080/

Where can this go?

It is now your turn to have a go and mash up your own solution. To help you get started, here are a bunch of ideas on how you can extend the chat application:

  • Chat Rooms

    • Add support for two chat rooms

    • Add support for unlimited chat rooms

    • Allow users to change the names of the chat rooms

  • Users

    • Give each user a randomly generated name - e.g. User ${Math.random()}

    • Next to each message, show the user who sent it - e.g.${user.name} says: ${message.text}

    • Show user connection and disconnection events as messages in the chat - e.g. ${user.name} joined the chat"

    • Remember the user's details if they leave the page and come back - e.g. localstorage or sessions

    • Allow the user to change their name

    • Show user name change events as messages in the chat - e.g.${oldName} changed their name to ${newName}

    • Give each user their own randomly selected color - e.g. style="color: hsl(50,50,50);"

    • Create a sidebar that lists all active members in the chat room

    • Allow users to specify their email

    • If a user has an email specified, display their avatar next to their username in the message listing

    • When a user changes their details, automatically update all prior mentions of their details

  • Abstractions

    • Experiment with pre-processors - DocPad could help with this

  • Messages

    • Relative times

    • Markdown support

    • Webkit notifications