How to Build a Real-Time Chat App with Socket.IO

Learn how to build a real-time chat app with Socket.IO using Node.js and a simple browser client, then extend it with typing indicators, private messages, message history, online presence, horizontal scaling via Redis, and integration with Pet Store API order events.

TRY NANO BANANA FOR FREE

How to Build a Real-Time Chat App with Socket.IO

TRY NANO BANANA FOR FREE
Contents

Chat applications demonstrate real-time communication perfectly. Messages should appear instantly, without refreshing. Socket.IO makes building this experience straightforward.

This tutorial builds a complete chat application using Socket.IO. You'll see the server setup, client implementation, and practical patterns for production use.

Setting Up the Server

Start with a basic Node.js server and add Socket.IO. The server handles client connections and message routing.

Initialize a new Node.js project:

npm init -y
npm install express socket.io

Create the server file:

const express = require('express');
const http = require('http');
const { Server } = require('socket.io');

const app = express();
const server = http.createServer(app);
const io = new Server(server);

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

// Store connected users
const users = new Map();

io.on('connection', (socket) => {
  console.log('New client connected:', socket.id);

  // Handle user login
  socket.on('user-join', (username) => {
    users.set(socket.id, username);

    // Notify everyone
    io.emit('system-message', {
      text: `${username} joined the chat`,
      timestamp: Date.now()
    });

    // Send user list to everyone
    io.emit('user-list', Array.from(users.values()));
  });

  // Handle chat messages
  socket.on('chat-message', (message) => {
    const username = users.get(socket.id);

    io.emit('chat-message', {
      id: Date.now(),
      username,
      text: message,
      timestamp: Date.now()
    });
  });

  // Handle typing indicator
  socket.on('typing', () => {
    const username = users.get(socket.id);
    socket.broadcast.emit('user-typing', { username });
  });

  // Handle disconnect
  socket.on('disconnect', () => {
    const username = users.get(socket.id);
    if (username) {
      io.emit('system-message', {
        text: `${username} left the chat`,
        timestamp: Date.now()
      });

      users.delete(socket.id);
      io.emit('user-list', Array.from(users.values()));
    }
  });
});

server.listen(3000, () => {
  console.log('Chat server running on port 3000');
});

The server handles several event types. User join messages notify everyone. Chat messages broadcast to all connected clients. Disconnect events clean up user data.

Building the Client

Create an HTML file for the client interface:

<!DOCTYPE html>
<html>
<head>
  <title>Pet Store Chat</title>
  <style>
    /* Basic styling for chat UI */
    #chat-container { max-width: 600px; margin: 0 auto; }
    #messages { height: 400px; overflow-y: scroll; border: 1px solid #ccc; }
    #input-area { display: flex; gap: 10px; }
    #message-input { flex: 1; padding: 10px; }
    #send-btn { padding: 10px 20px; }
    .message { padding: 8px; margin: 4px; border-radius: 4px; }
    .system-message { background: #f0f0f0; font-style: italic; }
    .user-message { background: #e3f2fd; }
    #join-screen, #chat-screen { display: none; }
  </style>
</head>
<body>
  <div id="chat-container">
    <div id="join-screen">
      <h2>Join Chat</h2>
      <input id="username-input" placeholder="Enter your name">
      <button id="join-btn">Join</button>
    </div>

    <div id="chat-screen">
      <h2>Pet Store Chat</h2>
      <div id="messages"></div>
      <div id="typing-indicator" style="color: gray; font-style: italic;"></div>
      <div id="input-area">
        <input id="message-input" placeholder="Type a message">
        <button id="send-btn">Send</button>
      </div>
    </div>
  </div>

  <script src="/socket.io/socket.io.js"></script>
  <script>
    const socket = io();
    let myUsername = '';

    // DOM elements
    const joinScreen = document.getElementById('join-screen');
    const chatScreen = document.getElementById('chat-screen');
    const usernameInput = document.getElementById('username-input');
    const joinBtn = document.getElementById('join-btn');
    const messagesDiv = document.getElementById('messages');
    const messageInput = document.getElementById('message-input');
    const sendBtn = document.getElementById('send-btn');
    const typingIndicator = document.getElementById('typing-indicator');

    // Show join screen initially
    joinScreen.style.display = 'block';

    // Join chat
    joinBtn.addEventListener('click', () => {
      myUsername = usernameInput.value.trim();
      if (myUsername) {
        socket.emit('user-join', myUsername);
        joinScreen.style.display = 'none';
        chatScreen.style.display = 'block';
      }
    });

    // Send message
    function sendMessage() {
      const text = messageInput.value.trim();
      if (text) {
        socket.emit('chat-message', text);
        messageInput.value = '';
      }
    }

    sendBtn.addEventListener('click', sendMessage);
    messageInput.addEventListener('keypress', (e) => {
      if (e.key === 'Enter') sendMessage();
    });

    // Typing indicator
    messageInput.addEventListener('input', () => {
      socket.emit('typing');
    });

    // Socket event handlers
    socket.on('chat-message', (msg) => {
      const msgDiv = document.createElement('div');
      msgDiv.className = 'message user-message';
      msgDiv.textContent = `${msg.username}: ${msg.text}`;
      messagesDiv.appendChild(msgDiv);
      messagesDiv.scrollTop = messagesDiv.scrollHeight;
    });

    socket.on('system-message', (msg) => {
      const msgDiv = document.createElement('div');
      msgDiv.className = 'message system-message';
      msgDiv.textContent = msg.text;
      messagesDiv.appendChild(msgDiv);
      messagesDiv.scrollTop = messagesDiv.scrollHeight;
    });

    socket.on('user-typing', (data) => {
      if (data.username !== myUsername) {
        typingIndicator.textContent = `${data.username} is typing...`;
        setTimeout(() => {
          typingIndicator.textContent = '';
        }, 2000);
      }
    });
  </script>
</body>
</html>

The client connects automatically when the page loads. When users join, they see the chat interface. Messages broadcast to everyone connected.

Running the Application

Start the server and open the chat in your browser:

node server.js

Open http://localhost:3000 in multiple browser tabs or windows. Each tab represents a different user. Messages appear instantly across all connections.

Advanced Features

Once basic chat works, enhance it with additional features.

Private messaging sends messages to specific users:

socket.on('private-message', ({ to, message }) => {
  const recipientSocket = getSocketByUserId(to);
  if (recipientSocket) {
    recipientSocket.emit('private-message', {
      from: socket.id,
      message
    });
  }
});

Message history stores recent messages and sends them to new connections:

const messageHistory = [];
const MAX_HISTORY = 100;

socket.on('chat-message', (msg) => {
  messageHistory.push(msg);
  if (messageHistory.length > MAX_HISTORY) {
    messageHistory.shift();
  }
  io.emit('chat-message', msg);
});

// Send history to new connections
socket.on('connection', () => {
  socket.emit('message-history', messageHistory);
});

Online presence shows who's currently active:

// Track online users
io.emit('online-users', Array.from(users.values()));

Scaling Considerations

At scale, chat applications require different architecture. Each server instance handles multiple connections. A message broker distributes messages across instances.

Use Redis adapter for Socket.IO scaling:

const { createAdapter } = require('@socket.io/redis-adapter');
const { createClient } = require('redis');

const pubClient = createClient({ url: 'redis://localhost:6379' });
const subClient = pubClient.duplicate();

io.adapter(createAdapter(pubClient, subClient));

This setup enables multiple Socket.IO servers to share messages. Users connect to any server and receive messages from all of them.

Pet Store API Integration

The Pet Store API supports Socket.IO for real-time notifications. Integrate chat with order updates, inventory changes, and more.

const socket = io('wss://api.petstoreapi.com', {
  path: '/socket.io',
  auth: { token: 'YOUR_JWT_TOKEN' }
});
  path: '/socket.io'
});

socket.on('order-update', (order) => {
  showNotification(`Order ${order.id} status: ${order.status}`);
});

The documentation at docs.petstoreapi.com shows available events and authentication.

Socket.IO simplifies real-time features dramatically. The fallback mechanism ensures it works everywhere. Your chat application reaches all users regardless of their network conditions.