WebSockets Chat App with Node.js and Socket.io

Traditional HTTP web architectures follow a strict request-response request lifecycle: the client sends an HTTP request, and the server returns a response before closing the connection. While this paradigm works well for standard REST APIs, it falls short for real-time applications such as live chat, collaborative editing boards, and real-time telemetry dashboards where servers need to push data instantly to connected clients.

The HTML5 **WebSocket** protocol solves this by establishing a persistent, full-duplex communication channel over a single TCP connection. In the Node.js ecosystem, **Socket.io** is the premier library that abstracts WebSocket management, automatic reconnection logic, and fallback transports. This comprehensive tutorial walks through building a fully functional real-time chat application from scratch using Node.js, Express, and Socket.io.

1. Project Initialization and Dependencies

Create a new project directory, initialize Node.js, and install `express` and `socket.io`.

C
# Terminal setup commands
mkdir websocket-chat-app
cd websocket-chat-app
npm init -y
npm install express socket.io
npm install --save-dev nodemon

2. Configuring the Node.js and Socket.io Server

Create a `server.js` file. Note that unlike standard Express apps where you listen on `app.listen()`, Socket.io requires attaching to the raw Node.js `http` server instance so both HTTP requests and WebSocket upgrade handshakes share the same port.

C
// server.js
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);

// Serve static frontend files from 'public' directory
app.use(express.static('public'));

// Handle Socket.io connection events
io.on('connection', (socket) => {
    console.log(`User connected: ${socket.id}`);

    // Broadcast incoming chat message to all connected clients
    socket.on('chat_message', (data) => {
        io.emit('chat_message', {
            username: data.username,
            message: data.message,
            timestamp: new Date().toLocaleTimeString()
        });
    });

    // Handle client disconnection
    socket.on('disconnect', () => {
        console.log(`User disconnected: ${socket.id}`);
    });
});

const PORT = process.env.PORT || 3000;
server.listen(PORT, () => {
    console.log(`🚀 Chat server running on http://localhost:${PORT}`);
});

3. Building the Frontend Chat UI and Client Script

Create a `public` directory and add an `index.html` file containing the chat interface and Socket.io client script.

C
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Real-Time Chat App</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css">
</head>
<body class="bg-gray-100 h-screen flex flex-col justify-between">
    <div class="bg-blue-600 text-white p-4 text-center font-bold text-xl">
        Node.js & Socket.io Live Chat
    </div>
    
    <div id="chat-window" class="flex-grow p-4 overflow-y-auto max-w-2xl mx-auto w-full space-y-3">
        <!-- Messages injected dynamically -->
    </div>

    <form id="chat-form" class="bg-white p-4 max-w-2xl mx-auto w-full flex shadow-lg">
        <input type="text" id="username" placeholder="Your Name..." class="border p-2 rounded mr-2 w-1/4" required>
        <input type="text" id="message" placeholder="Type a message..." class="border p-2 rounded flex-grow mr-2" required>
        <button type="submit" class="bg-blue-600 text-white px-6 rounded">Send</button>
    </form>

    <!-- Socket.io Client SDK -->
    <script src="/socket.io/socket.io.js"></script>
    <script>
        const socket = io();

        const form = document.getElementById('chat-form');
        const inputMsg = document.getElementById('message');
        const inputUser = document.getElementById('username');
        const chatWindow = document.getElementById('chat-window');

        form.addEventListener('submit', (e) => {
            e.preventDefault();
            if (inputMsg.value && inputUser.value) {
                socket.emit('chat_message', {
                    username: inputUser.value,
                    message: inputMsg.value
                });
                inputMsg.value = '';
            }
        });

        socket.on('chat_message', (data) => {
            const div = document.createElement('div');
            div.className = "p-2 bg-white rounded shadow-sm";
            div.innerHTML = `<span class="font-bold text-blue-600">${data.username}</span> <span class="text-xs text-gray-400">[${data.timestamp}]</span>: <p>${data.message}</p>`;
            chatWindow.appendChild(div);
            chatWindow.scrollTop = chatWindow.scrollHeight;
        });
    </script>
</body>
</html>

Conclusion

By combining Node.js, Express, and Socket.io, developers can build robust, bi-directional real-time chat applications with minimal code complexity and high scalability.