Simple Chat application using node.js and socket.io
Older Article
This article was published 13 years ago. Some information may be outdated or no longer applicable.
After writing a few posts about a websocket-based multiplayer game, I figured it’d make sense to step back and cover websockets from the ground up. The best way to learn how they work? Build a simple real-time chat application. That’s exactly what we’ll do here.
We all remember writing document.write("Hello World"); some 10 (or 15?) years ago. That was a typical JavaScript statement for me back then. And let’s not forget the classic var today = new Date(); document.write(today);. JavaScript grew from there into DOM manipulation via libraries like Prototype and jQuery. AJAX callbacks, carousels, lightboxes… the list goes on. Then the language kept evolving, and we got Node.js, AngularJS, Backbone.js and a whole generation of frameworks that push the boundaries of what’s possible.
When I first heard about Node.js, my reaction was: “what on earth is server-side JavaScript?” I couldn’t picture it based on my (admittedly limited) understanding of the language at the time. Once I started working with it, everything clicked. Quick background: Node.js runs on Google’s V8 JavaScript engine, which is blazingly fast. A basic Node.js application lets you create your own web server (no Apache or Nginx required), serving content much more quickly. Drop socket.io into the mix and you’ve got a solution that works across every browser and every mobile device. Socket.io pushes messages back and forth between a “client” (browser, device, whatever) and the “server” (the websocket server invoked by socket.io). The most natural thing to build with it? Chat. Every connected client pushes messages to the server, and the server pushes replies back to all connected clients.
Here’s what the application does:
- People can enter their names in the chat
- They are then connected to the websocket server
- The websocket server sends a confirmation message to the client and also notifies other connected clients that someone else has joined
- Users are able to exchange messages
- Connected clients are notified if a person leaves the chat
- Connected clients are notified if the websocket server shuts down
Nothing exotic. Let’s write some code.
First, we’ll create a websocket server that listens on IP “1.2.3.4” port 1223. We’ll also set up a hash to track connected users:
var io = require('socket.io');
var socket = io.listen(1223, '1.2.3.4');
var people = {};
Every action while the websocket server is running has to sit inside socket.on("connection", function(client) {...});. Our “actions” are:
- join
- send
- disconnect
To learn more about the exposed events in socket.io (both server and client side), please see the following page on GitHub.
Let’s build those three actions:
socket.on('connection', function (client) {
client.on('join', function (name) {
people[client.id] = name;
client.emit('update', 'You have connected to the server.');
socket.sockets.emit('update', name + ' has joined the server.');
socket.sockets.emit('update-people', people);
});
client.on('send', function (msg) {
socket.sockets.emit('chat', people[client.id], msg);
});
client.on('disconnect', function () {
socket.sockets.emit('update', people[client.id] + ' has left the server.');
delete people[client.id];
socket.sockets.emit('update-people', people);
});
});
The server listens for a “join” event emitted by the client (triggered by a button click, as we’ll see). The join handler receives a name, stores it in the people hash keyed by the socket’s unique ID, then emits messages. Note the difference: client.emit() sends only to the current client. socket.sockets.emit() broadcasts to everyone. So “You have connected to the server” only appears for the person who just joined, while the other two emits reach all connected clients.
The “send” function broadcasts a message (along with the sender’s name) to every connected client.
Disconnect does what you’d expect: notifies everyone that someone left, removes them from the people hash, and updates the connected users list.
Now for the client side. First, the HTML:
<!DOCTYPE html>
<html lang="en">
<head>
<script src="http://1.2.3.4:1223/socket.io/socket.io.js"></script>
<script src="http://1.2.3.4/js/bootstrap.js"></script>
<script src="http://1.2.3.4/js/jquery.js"></script>
<link rel="stylesheet" type="text/css" href="http://1.2.3.4/css/bootstrap.css">
<script>
<!-- We will add this later -->
</script>
<body>
<div class="row">
<div class="span2">
<ul id="people" class="unstyled"></ul>
</div>
<div class="span4">
<ul id="msgs" class="unstyled">
</div>
</div>
<div class="row">
<div class="span5 offset2" id="login">
<form class="form-inline">
<input type="text" class="input-small" placeholder="Your name" id="name">
<input type="button" name="join" id="join" value="Join" class="btn btn-primary">
</form>
</div>
<div class="span5 offset2" id="chat">
<form id="2" class="form-inline">
<input type="text" class="input" placeholder="Your message" id="msg">
<input type="button" name="send" id="send" value="Send" class="btn btn-success">
</form>
</div>
</div>
</div>
</body>
</html>
Nothing surprising. A simple HTML page using the Bootstrap library.
Now the JavaScript. Drop this between the script tags:
$(document).ready(function(){
var socket = io.connect("1.2.3.4:1223");
$("#chat").hide();
$("#name").focus();
$("form").submit(function(event){
event.preventDefault();
});
$("#join").click(function(){
var name = $("#name").val();
if (name != "") {
socket.emit("join", name);
$("#login").detach();
$("#chat").show();
$("#msg").focus();
ready = true;
}
});
$("#name").keypress(function(e){
if(e.which == 13) {
var name = $("#name").val();
if (name != "") {
socket.emit("join", name);
ready = true;
$("#login").detach();
$("#chat").show();
$("#msg").focus();
}
}
});
socket.on("update", function(msg) {
if(ready)
$("#msgs").append("H2M_LI_HEADER " + msg + "
");
})
socket.on("update-people", function(people){
if(ready) {
$("#people").empty();
$.each(people, function(clientid, name) {
$('#people').append("H2M_LI_HEADER " + name + "
");
});
}
});
socket.on("chat", function(who, msg){
if(ready) {
$("#msgs").append("H2M_LI_HEADER **" + who + "** says: " + msg + "
");
}
});
socket.on("disconnect", function(){
$("#msgs").append("H2M_LI_HEADER **The server is not available**
");
$("#msg").attr("disabled", "disabled");
$("#send").attr("disabled", "disabled");
});
$("#send").click(function(){
var msg = $("#msg").val();
socket.emit("send", msg);
$("#msg").val("");
});
$("#msg").keypress(function(e){
if(e.which == 13) {
var msg = $("#msg").val();
socket.emit("send", msg);
$("#msg").val("");
}
});
});
Update!
The code above works if you’re running it from an existing server. If you want to skip installing Apache (or any other server), you can spin one up with Node.js:
var http = require('http'),
fs = require('fs'),
io = require('socket.io'),
index;
fs.readFile('./chat.html', function (err, data) {
if (err) {
throw err;
}
index = data;
});
var server = http.createServer(function(request, response) {
response.writeHeader(200, {"Content-Type": "text/html"});
response.write(index);
response.end();
}).listen(1223);
//and replace var socket = io.listen(1223, "1.2.3.4"); with:
var socket = io.listen(server);
There’s more going on here than in the HTML. First, we connect to the running websocket (make sure to run node chat.js first) and hide a few elements with standard jQuery. The join button (#join) grabs the name and emits it to the server. (The server-side code we wrote earlier picks this up.) The update and update-people handlers are both triggered by the server, and on the client side we just update a couple of lists.
The chat handler (also emitted from the server) takes the messages from every client and displays them.
To make the flow crystal clear, here’s the logical processing order. Assume the websocket server is running and the client is connected:
$("#send").click(function() {
socket.emit("send", msg);
}
User clicks “send.” We emit the “send” event to the server, passing the message as an argument.
client.on('send', function (msg) {
socket.sockets.emit('chat', people[client.id], msg);
});
Server picks up the “send” event. It broadcasts a “chat” event to all connected clients, passing the message and the sender’s name.
socket.on('chat', function (who, msg) {
$('#msgs').append(
"<li><strong><span class='text-success'>" +
who +
'</span></strong> says: ' +
msg +
'</li>'
);
});
Back on the client, we catch the “chat” event and print the sender’s name and message to the screen inside a <div>.
That’s the fundamental loop of websocket communication. Hope you found this useful. Grab the code from GitHub.