Introduction to Socket.IO: Building a Simple Chat App

Joshua Hall

In this article we’ll be exploring the basic of setting up a realtime chat app using Socket.IO.

Since, for the sake of simplicity, we won’t be including any backend storage for our messages. Previously sent messages just won’t be visible by any new pages, to make it so that there’s no saved data to load in, but to learn more about setting up a simple backend for your data using Mongoose and the MongoDB Atlas database, you can check out my article here

Prerequisites

All we need is a basic understanding of Express, to set up the server, which we have another article on, and the official docs are always helpful too.

Installation

$ npm i express socket.io

File Setup

Here’s the file layout for our simple project

public 📂
  > index.html
  > chat.js - Handles our front-end and is imported into index.html.
server.js - Manages the server and Socket.IO interactions.

Let’s setup our messenger, to avoid needing another file for styling, I’ll be using the Tailwind CSS framework.

All we need are our imports from Tailwind, the socket.IO front-end client (different from what we installed using npm), an empty div to render our new messages into, and a simple form for the user’s name and message.

index.html

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <meta http-equiv="X-UA-Compatible" content="ie=edge" />
  <link rel="stylesheet" href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" />
  <title>Chat App</title>
</head>

<body class="bg-gray-900 flex justify-center mb-32">

  <div class="text-white container mt-10" id="chat"></div>

  <form class="flex flex-col md:flex-row w-full h-64 md:h-24 bottom-0 container fixed">
    <input class="bg-transparent text-white w-full md:w-40 h-24 pl-2" type="text" name="name" placeholder="name"
      required />
    <input class="bg-transparent text-white w-full md:w-screen h-full md:h-24 pb-32 md:pb-0 pl-2" type="text"
      name="message" placeholder="message" />
    <button class="bg-purple-700 text-white p-3 rounded h-16" type="submit" name="send">Send</button>
  </form>
  
  <script src="https://cdn.jsdelivr.net/npm/socket.io-client@2.2.0/dist/socket.io.js"></script>
  <script src="./chat.js"></script>
</body>
</html>

We’re going to keep it simple here, for now. We just need to initialize Socket.IO on the client-side using the io() function.

chat.js

io()

Bam, that’s it for now. Chat.js will be very useful later for moving data between the front and back-end of our site.

After we’ve set up a basic Express server we need to import the path and http Node modules. We’ll convert our normal server into a more useful form for Socket.IO, and make a new server with that. We’ll then start the server on port 3000 using app.use.

server.js

const express = require('express')
const http = require('http')
const path = require('path')
const socketio = require('socket.io')

const app = express()
const server = http.createServer(app) // io requires raw http
const io = socketio(server) // Setup Socket.io's server

// When we run the server our public file will also be ran
const publicDirectoryPath = path.join(__dirname, './public')
app.use(express.static(publicDirectoryPath))

io.on('connection', socket => {
  console.log("new connection")
})

const port = process.env.PORT || 3000
server.listen(port, () => console.log('Server is running...'))

Try opening a new page on localhost:3000, whenever there’s a new tab or a reload, the “new connection” message should appear in the terminal.

Basic Functions

There are only a few basic functions that we’ll need for our example:

  • emit() Takes the event you want called and what you want to send with it, like a message.
  • on() Takes an event name and a callback function with what do do when the event is triggered. Takes the connection and disconnect events by default but you can add custom events.
  • broadcast.emit() Sends to every user but the sender.

Sending and Rendering Messages

Basically, when we get the sendMessage event from chat.js we emit a new event to the client to render the new message. So, while only one client called the event, all users on the server will get the message.

With broadcast be can log whenever someone has joined the server to everyone but the new user.

server.js

io.on('connection', socket => {
  socket.broadcast.emit("showMessage", { name: 'Anonymous', message: 'A NEW USER HAS JOINED' })

  socket.on('sendMessage', message => io.emit('showMessage', message))
})

Client-side

In here we’ll set our io instance to a more useful variable. We’re also going to setup and emit when we submit the form, sending an object with our data to server.js which is then sent back to all clients on the ‘showMessage’ event. With that all we need to do is render it to the screen:

chat.js

const socket = io()

// get all of our elements
const chat = document.querySelector('#chat')
const form = document.querySelector('form')
const name = form.name
const message = form.message
const send = form.send

form.addEventListener('submit', e => {
  e.preventDefault()

  //disable while the message is being sent.
  send.classList.remove('bg-purple-700')
  send.classList.add('cursor-not-allowed', 'bg-purple-500')

  socket.emit('sendMessage', {
    name: name.value,
    message: message.value
  })

  message.value = ''
  message.focus()
})

// Just creating a new element for our message and appending it to the chat and re-enabling the send button. Styling is optional of course.
socket.on('showMessage', message => {
  const newMessage = document.createElement('div')
  const user = document.createElement('h3')
  const text = document.createElement('p')

  newMessage.classList.add('flex', 'items-center', 'mt-5')
  user.classList.add('bg-blue-600', 'p-3', 'mr-10', 'w-40', 'rounded', 'self-start')
  user.innerHTML = message.name
  text.classList.add('w-4/5')
  text.innerHTML = message.message

  newMessage.appendChild(user)
  newMessage.appendChild(text)
  chat.appendChild(newMessage)

  // Re-enable button after message is received
  send.classList.remove('cursor-not-allowed', 'bg-purple-500')
  send.classList.add('bg-purple-700')
})

Now if you open localhost:3000 on two different tabs and send a message, they should be instantaneously rendered on both.

Conclusion

With relatively little code we can make a basic messaging system without needing to send and wait for data. If something didn’t work for you you can look at my live example and check out the code on Github.

  Tweet It

🕵 Search Results

🔎 Searching...

Sponsored by #native_company# — Learn More
#native_title# #native_desc#
#native_cta#