This past week at Viking, we learned about web sockets using socket.io to build a chat room. I’ll boil that project done to its most basic for this post and explain some of the trickier parts in this post. Web sockets are a really interesting topic and also make a fun project to help learn about the difference between statefulness and statelessness and a good refresher on emitting and listening to custom events. What’s more, it’s really satisfying to see realtime data feeds updating multiple brower windows at once. Let’s dive in.
Where standard get methods to a server are stateless and close after completion, web sockets keep the connection alive and update the client and server through events and listeners.
It’s important to keep that in mind as you configure both the server and client to respond and emit these events. The server setup is fairly standard compared to other node applications — you’ll just need to understand that in addition to your standard routing handlers, you’ll also be setting up a concurrent event listener and emitter.
Here’s the setup for the most basic anonymous chatting. It’s just a simple demonstration to show the power of sockets.
Server side:
Here’s a quick setup for the server side. Credit to Viking for the bones of the code below. Take note of how the Express instance is passed into the http library’s createServer’s event listener. That server variable that we’ve saved is then passed into socket.io. This step is important, so take care to get the details correct.
Also take note of the ‘connection’ event listener attached to io. Inside of that callback is where you’ll want to set up the client listeners that will respond to events in the browser. These can either be emitted back to that individual client or all clients currently connected. In the code below, we’re using io.emit
to send to all clients so that they can see instant updates. That could easily be client.emit
if you were looking to only update that client.
const express = require("express");const app = express();const http = require("http");const server = http.createServer(app);const io = require("socket.io")(server);
app.use("/socket.io",express.static(__dirname + "/node_modules/socket.io-client/dist/"));
app.get("/", (req, res) => {res.sendFile(__dirname + "/index.html");});
let text = "";
io.on("connection", client => {console.log("New connection!");
client.on("new text", newText => {text += newText + "<br>";io.emit("updated messages", text);});});
server.listen(3000);
Since this is for a basic chat, we’re adding to the text variable that will be stored in memory for as long as the process runs. Keep in mind that this is obviously not a good solution, so I’d suggest looking into Redis if you’re looking for a more robust persistence layer. You’ll see that we’re sending that text string back all clients with this line:
io.emit("updated messages", text)
The ‘updated messages’ is an event that will be paired with an event listener in our browser code to listen and update once that event is emitted.
Client side:
Start with an index.html with a reference to socket.io in the head. Feel free to use this code if your server it set up as it is above.
<script src='/socket.io/socket.io.js'></script>
This will give us access to our server with the following code that will need to be inserted in a script tag at the end of the html as follows. Feel free to include the code inline inside of the script tag or extract that into a separate file. I’ve kept it inline for this basic demo:
<script>let socket = io.connect("http://localhost:3030");
Here’s our ‘updated messages’ event from before. The socket that we defined above now listens for that event and updates the html using jQuery. I’ve omitted the rest of the html here, so feel free to set that up as you please. Regardless, when that event is emitted by the server, the text in the markup will be updated.
socket.on("updated messages", fullText => {$("#story").html(fullText);});
We’ll now set up an event emitter on the client that matches the listener in the server. In this case, I’m using a text form and preventing it from it’s default action of submitting. Note again that the ‘new text’ listener is completely arbitrary in its naming, but needs to match the listener on the backend.
$("button").on("click", e => {e.preventDefault();let message = $("input").val();socket.emit("new text", message);});</script>
This completes the handshake. Take a minute to look back at the server setup as well to understand the circle of events. I would also suggest playing with the listeners and events. It’s fairly easy once you’ve done it a few times and can see the whole picture.
These can obviously become cumbersome when you have many events, so take care to keep your code organized and modular with larger applications. You now have access to the most basic chat window, so fire up your node server, open a few windows to your localhost, and watch the magic happen as you get realtime feedback without refreshing the page. Pretty cool stuff!