Learn to Code via Tutorials on Repl.it

← Back to all posts
Anonymous Chat in NodeJS
LeonDoesCode (270)

Another Chatting System, Really?

Yep, why? Because they are an easy way to demonstrate how to communicate over a network, and require such little code. However, we don't need a dozen new Chat Applications, especially with all the ones we already have now days. So be creative in the ways you use this new ability, I beg of you.

This is going to be a longer one, so let's begin!

Server-Client

So when we create online applications, we will normally use a Server-Client setup. I say normally, because we can also use a Peer-to-Peer setup, but we won't be doing this today.

Server-Client setups, as you can guess, require a Server and Client. Where the Client will connect to the Server. The Server and Client will then receive and send data when necessary. This can be from sending input data from the Client to the Server, or the Server sending data to a Client when another Client sends data.

Let's look at what we will need to do this.

Requires

So we will need to require some stuff. We will also be adding a bit of flair into our Chat System, so let's also have a look at that. First, let's require all the module we will be using:

const express = require("express");
const app = express();
const http = require("http").createServer(app);
const io = require("socket.io")(http);
const md = new require('markdown-it')();

express: Will help to serve the static Chat website to the Client.
http: Will listen for Clients.
io: Will be how the Server and Client communicate.
md: Just to make it so that we have markdown support

Now that we know what modules we will be working with, let's start making this chat!

Hosting the Website

So, before we can do anything, we need a website which will act as our Client. To do this, let's first add a folder called public int the "Files" section. In the folder public, add three files called index.html, style.css, and script.js. These files will be our website. I'm not going to explain the HTML and CSS files in too much detail, but here they are:

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta http-equiv="Content-Security-Policy" content="script-src 'self' https://code.jquery.com https://fonts.googleapis.com;">

    <title>Chatter | </title>

    <!-- CSS -->
    <link href="style.css" rel="stylesheet" type="text/css" />
  </head>

  <body>
    <div id="messages"></div>
    <input id="send" type="text" autocomplete="off" placeholder="Type your message here..." autofocus="true" />

    <!-- JS -->
    <script src="https://code.jquery.com/jquery-3.4.1.min.js" integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script>

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

The big thing to note that we include in the HTML file, it the line <script src="/socket.io/socket.io.js"></script>. This will get the socket.ios JS file that is give to our website when it joins as a Client. We also have an input with and id of send, this is where the user can input and send their message. Finally, we have a div with an id of messages, this is where all the messages will be stored.

There is one more big thing here, we include a Content-Security-Policy in one of the meta tags. We make it so that the website can only receive scripts from:

Without this, anyone could preform a XSS attack, enabling them to send scripts from another site to ours. This is a big security risk, so make sure to do this on any website.

style.css

@import url('https://fonts.googleapis.com/css?family=Lato&display=swap');

html, body {
  width: 100%;
  height: 100%;

  margin: 0;
  padding: 0;

  font-family: 'Lato', sans-serif;
  font-size: 15px;

  background-color: #353535;
}

#message * {
  display: inline;
  color: inherit;
  max-width: 80%;
}

#messages {
  padding: 20px;

  position: absolute;

  top: 20px;
  left: 10%;
  right: 10%;

  color: #d2d2d2;

  border-radius: 10px;
}

#send {
  width: 80%;
  height: 50px;

  padding: 10px;

  position: fixed;

  bottom: 20px;
  left: 10%;
  right: 10%;

  font-size: 20px;

  background-color: #454545;
  color: #d2d2d2;

  border: none;
  border-radius: 10px;
  outline: none;

  resize: none;
}

This will make the background dark, and the writing light, it's just easier on the eyes. It will also float the input box above the messages, at the bottom of the screen. Just a nice little feature to make it look a bit nicer. Oh, it also give the text a nice font called "Lato" from "Google Fonts".

Client

Now we can create the Client's code. This will be in the script.js file that will be hosted by the Server.

function getRandomColor() {
  var letters = '0123456789ABCDEF';
  var color = '#';
  for (var i = 0; i < 3; i++) {
    color += letters[Math.floor(Math.random() * 16)] + "f";
  }
  return color;
}

We are using JQuery here to make it much easier to read. This let's you focus more on the important code.

As a nice touch, we create a function called getRandomColor. this will give the user a random colour for their text, other users will have their own. This colour is generated every time the user refreshed, which makes it so that they are still even more anonymous. This colour will be transferred along with the message that the user sends, so that it is the same for everyone.

const socket = io();
const mycolor = getRandomColor();

Now we create our io object, this will allow us to communicate with the Server. We will call it socket so that it makes a bit more sense when we look at the Server side of things. We will also create mycolor, which will call the getRandomColor function. This will be the colour that our text is printed in.

socket.emit("join", window.location.hash);
document.title += " " + window.location.hash;

Here we use socket.emit(). This function sends out an even with a "name" and some data. This event's "name" is "join", and it's data is the URL's hash (any characters after the "#" symbol in the URL). The event will be recived by the Server, and in this case, it will add the socket to a room. More on that in the Server (as that is where the processing is done).

As a nice touch, we also add that same hash to the end of the URL, so that the user knows what room they are in.

$("#send").on("keypress", e => {
  if(e.which == 13){
    socket.emit("message", {color:mycolor, text:document.getElementById("send").value});
    $("#send").val("");
  }
});

Next, we check if the "Enter" key (key code 13) is pressed in the send input. If so, we send another event, this time with the "name" "message", it's data will be a JS Object. This Object will contain the color of the user who sent it, and the text that the user is sending. This text comes from the send input too. We then set the value of send to nothing, so that they can enter a new message.

socket.on("new message", (data) => {
  document.getElementById("messages").innerHTML = `<div id=\"message\" style=\"color: ${data.color};\">${+new Date() + ": " + data.text}</div>${document.getElementById("messages").innerHTML}`;
});

Lastly, we check if an even has been sent to the socket. We do this with socket.on(). This function will receive a "name" and data. It will then call a callback function, giving the data in as its argument. This is where we will add a div to the messages div. We give the new div an id of message, so that it gets styled appropriately. We also add our own style, which will be color, we get this colour from the data. We also add the text data which also comes from the data we get.

Now onto the server!

Server

After we require all the things from the beginning of the tutorial, we need to write the Server code. So lets begin!

app.use(express.static("public"));

The first thing is to make express send the Client the static site. To do that we use app.use(), and pass it the function express.static with the name of the folder that the static files are stored in. We put them in "public", so let's write that folder in.

let rooms = {};
let welcome = "Looking for a completely Anonymous Chat, well you've come to the right place! Refresh for a new colour! Add #[room_name] to the end of the url to join/create a room! Oh, and there's markdown!";

let port = 3000;

http.listen(port, () => {
  console.log(`listening on *:${port}`);
});

Now we can add some of the smaller stuff. This includes:

  • a JS Object called rooms, where we will store all the rooms and their messages,
  • a string called welcome which stores a welcome message that we send to the user,
  • and a integer called port which will be the port we connect too.

We will also use http.listen() with the port to start listening for Clients. In between the variables and listen, let's add our code:

io.on("connection", (socket) => {
  console.log("a user connected");

  socket.on("disconnect", () => {
    console.log("a user disconnected");
  });
});

Firstly, let's acknowledge when a Client connects and disconnected. These events are automatically sent when the Client joins or leaves the website. Now we can move onto the interesting part!

  socket.on("join", room => {
    if(!rooms[room]) {rooms[room] = {}; rooms[room].old = []; rooms[room].sockets = [];}
    socket.room = room;
    rooms[socket.room].sockets.push(socket);

    for(let old of rooms[socket.room].old) {
      socket.emit("new message", old);
    }

    socket.emit("new message", {color:"#ffffff", text:welcome});
  });

Here we check to see when the "join" event is received from the Client. If it is, then we get the room they joined and make them join it. If it doesn't exits, then we make it automatically for them, and then join them to it. We then get the old messages from that room (which will be none if it was just created), and send them too the Client using the "new message" event. We will sent the color and text of the message. Lastly, we send only the new Client the welcome message, so that they understand how the chat works.

  socket.on("message", data => {
    try {
      data.text = md.render(data.text);
      for(let s of rooms[socket.room].sockets) {
        s.emit("new message", data);
      }
      rooms[socket.room].old.push(data);
    }
    catch(err) {
      socket.emit("new message", {color:"#ffffff", text:"There was an error!"});
    }
  });

Here we are receiving a message from the Client. We create a try-catch statement (just in case there's an error with the message) , and then process the message. We use the Markdown Renderers md.render() to render the text from data. we then loop through all the sockets connected to that room, and send that message too them. We also send it ti the Client who sent as they are waiting to receive it before the show it. We then add the message to the old messages for that room.

I hope you found this useful in helping you to understand how events work using Socket.io!

Conclusion

As of writing this, I've notice that I don't remove the Sockets from the rooms when they disconnect. This was completely accidental, so think of it as a homework task for you to do yourself. As always, I hope that you found this useful! And be creative you awesome people!

Hope you have a great day!

P.S
This tutorial is one in a series of tutorials suggested by @rediar .
If you have any suggestions for tutorials, leave them in the comments and I'll be sure to have a look. If you like one in the comments, then give it an up vote to show that you want to see it. It makes my life so much more easier. Thanks in advance!

Commentshotnewtop