Building a Chat Feature with WebSockets and Socket.io

Table of Contents
Big thanks to our contributors those make our blogs possible.

Our growing community of contributors bring their unique insights from around the world to power our blog. 

Introduction

Real-time communication is at the heart of modern web applications—whether it’s a group chat, live notifications, or collaborative editing. Traditional HTTP requests simply can’t keep up with the low-latency, bidirectional data flow required for these features. That’s where WebSockets and Socket.io come in. In this post, you’ll learn what WebSockets are, why Socket.io makes your life easier, and how to build a robust, scalable chat feature from scratch. We’ll walk through server and client setup, room management, message broadcasting, and best practices for authentication and deployment. By the end, you’ll have a fully functional chat module ready to integrate into your next Node.js project.

Understanding WebSockets vs. HTTP

What Are WebSockets?

  • Persistent connection: Unlike HTTP’s request-response model, WebSockets keep a single TCP connection open for continuous, two-way data exchange.
  • Low latency: Ideal for scenarios requiring instant updates—chat, stock tickers, gaming, IoT dashboards.
  • Binary & text support: WebSockets transmit both string and binary data efficiently.

Why Socket.io?

While raw WebSockets are powerful, they lack features every production app needs:

  • Automatic reconnection when the connection drops.
  • Multiplexing through “namespaces” to separate concerns.
  • Room support for grouping sockets.
  • Fallback transports (e.g., long polling) for older browsers.
  • Middleware for authentication and logging.

Expert Insight:
“Socket.io abstracts away the quirks of different browsers and network conditions, letting developers focus on real-time logic rather than low-level socket handling.”
— Jane Doe, Senior Backend Engineer

Project Setup

1. Initialize Your Node.js Project

bashCopyEditmkdir chat-app
cd chat-app
npm init -y
npm install express socket.io cors
  • express: Serves HTTP endpoints and static files.
  • socket.io: Provides real-time, event-based communication.
  • cors: Configures cross-origin resource sharing for your client.

2. Boilerplate Express Server

Create server.js:

javascriptCopyEditconst express = require('express');
const http = require('http');
const cors = require('cors');
const socketIo = require('socket.io');

const app = express();
app.use(cors());
const server = http.createServer(app);
const io = socketIo(server, {
  cors: { origin: '*' }
});

const PORT = process.env.PORT || 5000;
server.listen(PORT, () => console.log(`Server listening on port ${PORT}`));

Building the Chat Logic

3. Handling Socket Connections

Inside server.js, add connection handlers:

javascriptCopyEditio.on('connection', (socket) => {
  console.log(`New client connected: ${socket.id}`);

  socket.on('joinRoom', ({ username, room }) => {
    socket.join(room);
    socket.username = username;
    socket.room = room;
    socket.to(room).emit('message', {
      user: 'system',
      text: `${username} has joined the room.`
    });
  });

  socket.on('sendMessage', (message) => {
    const { username, room } = socket;
    io.to(room).emit('message', { user: username, text: message });
  });

  socket.on('disconnect', () => {
    if (socket.room) {
      socket.to(socket.room).emit('message', {
        user: 'system',
        text: `${socket.username} has left the room.`
      });
    }
    console.log(`Client disconnected: ${socket.id}`);
  });
});
  • connection event: Fires when a client connects.
  • joinRoom custom event: Adds a user to a room and broadcasts a welcome message.
  • sendMessage event: Broadcasts a user’s message to everyone in the same room.
  • disconnect event: Cleans up and notifies peers.

4. Creating the Client

In your project root, create public/index.html:

htmlCopyEdit<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <title>Socket.io Chat</title>
  <style>
    /* Basic styles for clarity */
    body { font-family: Arial, sans-serif; margin: 20px; }
    #chat { border: 1px solid #ccc; padding: 10px; height: 300px; overflow-y: scroll; }
    #messageForm { margin-top: 10px; }
  </style>
</head>
<body>
  <h1>Chat Room</h1>
  <div>
    <label>Username: <input id="username" /></label>
    <label>Room: <input id="room" /></label>
    <button id="joinBtn">Join</button>
  </div>
  <div id="chat"></div>
  <form id="messageForm">
    <input id="message" autocomplete="off" placeholder="Type a message..." />
    <button>Send</button>
  </form>

  <script src="/socket.io/socket.io.js"></script>
  <script>
    const socket = io();
    const chat = document.getElementById('chat');
    const messageForm = document.getElementById('messageForm');
    const messageInput = document.getElementById('message');
    let username, room;

    document.getElementById('joinBtn').onclick = () => {
      username = document.getElementById('username').value.trim();
      room = document.getElementById('room').value.trim();
      if (username && room) {
        socket.emit('joinRoom', { username, room });
      }
    };

    socket.on('message', ({ user, text }) => {
      const div = document.createElement('div');
      div.textContent = `${user}: ${text}`;
      chat.appendChild(div);
      chat.scrollTop = chat.scrollHeight;
    });

    messageForm.addEventListener('submit', (e) => {
      e.preventDefault();
      const msg = messageInput.value;
      if (msg) {
        socket.emit('sendMessage', msg);
        messageInput.value = '';
      }
    });
  </script>
</body>
</html>
  • Static serve: In server.js, add: javascriptCopyEditapp.use(express.static('public'));
  • Client logic: Captures username/room, joins the room, and displays incoming messages.

Advanced Features

5. User Lists & Room Data

Track active users in a room:

javascriptCopyEditconst users = {}; // { room1: [user1, user2], room2: [...] }

socket.on('joinRoom', ({ username, room }) => {
  users[room] = users[room] || [];
  users[room].push(username);
  io.to(room).emit('roomData', { room, users: users[room] });
});

socket.on('disconnect', () => {
  if (socket.room) {
    users[socket.room] = users[socket.room].filter(u => u !== socket.username);
    io.to(socket.room).emit('roomData', { room: socket.room, users: users[socket.room] });
  }
});

On the client, listen for roomData:

javascriptCopyEditsocket.on('roomData', ({ room, users }) => {
  // Update a user list UI element
  console.log(`Users in ${room}: ${users.join(', ')}`);
});

6. Message Persistence

For production, integrate a database (e.g., MongoDB) to store chat history:

javascriptCopyEdit// Example with Mongoose
const mongoose = require('mongoose');
const Message = mongoose.model('Message', new mongoose.Schema({
  room: String,
  user: String,
  text: String,
  timestamp: { type: Date, default: Date.now }
}));

// Save on sendMessage
socket.on('sendMessage', async (message) => {
  const msg = await Message.create({ room: socket.room, user: socket.username, text: message });
  io.to(socket.room).emit('message', msg);
});

Security & Best Practices

Authentication & Authorization

  • JWT Middleware: Verify tokens before allowing a socket connection. javascriptCopyEditio.use((socket, next) => { const token = socket.handshake.auth.token; // verify JWT... next(); });
  • Room validation: Ensure users can only join rooms they’re authorized for.

Scaling with Multiple Instances

  • Redis Adapter: Share socket events across Node.js instances. bashCopyEditnpm install socket.io-redis javascriptCopyEditconst redisAdapter = require('socket.io-redis'); io.adapter(redisAdapter({ host: 'localhost', port: 6379 }));

Performance Tips

  • Message batching: Group rapid-fire events to reduce network overhead.
  • Compression: Enable perMessageDeflate in Socket.io options.
  • Namespace segregation: Use namespaces (/chat, /notifications) to load only necessary logic on each page.

Conclusion

Building a chat feature with WebSockets and Socket.io empowers you to deliver lightning-fast, two-way communication in your web apps. You’ve seen how to set up a Node.js server, manage rooms, broadcast messages, and implement advanced functionality like user lists and persistence. With security measures like JWT authentication and scaling solutions such as Redis adapters, your chat service is ready for production. Integrate this module into your application, and watch engagement soar as users enjoy seamless, real-time interactions.

Let's connect on TikTok

Join our newsletter to stay updated

Sydney Based Software Solutions Professional who is crafting exceptional systems and applications to solve a diverse range of problems for the past 10 years.

Share the Post

Related Posts