Freedom Sale
Independence Day Special — Unlock the AI Path 70% off our most popular AI course · Limited time offer
--Days
--Hrs
--Min
--Sec
Claim Your Discount
✦ Intermediate ⏱ 45 min

💬 Build a Real-Time Chat App with Socket.io

🎯 What You'll Build

A live group chat room where messages appear instantly for all connected users — built with Node.js, Express, and Socket.io. Open in multiple browser tabs to test.

📋 What You'll Need

1

Set up the project

Create the project folder, initialise npm, and install the two packages you need.

mkdir chat-app && cd chat-app
npm init -y
npm install express socket.io
added 23 packages in 2s
2

Create the server with Express and Socket.io

The server serves the HTML page and handles WebSocket connections.

// server.js
const express = require('express');
const http    = require('http');
const { Server } = require('socket.io');
const path    = require('path');

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

app.get('/', (req, res) => {
  res.sendFile(path.join(__dirname, 'index.html'));
});

// Track connected users
let userCount = 0;

io.on('connection', (socket) => {
  userCount++;
  console.log(`User connected — total: ${userCount}`);

  // Tell everyone someone joined
  io.emit('user-count', userCount);
  io.emit('system-message', 'A new user joined the chat');

  // Receive a message and broadcast to ALL users
  socket.on('chat-message', (data) => {
    io.emit('chat-message', data);
  });

  // Typing indicator — broadcast to everyone EXCEPT sender
  socket.on('typing', (username) => {
    socket.broadcast.emit('typing', username);
  });

  socket.on('stop-typing', () => {
    socket.broadcast.emit('stop-typing');
  });

  socket.on('disconnect', () => {
    userCount--;
    io.emit('user-count', userCount);
    io.emit('system-message', 'A user left the chat');
    console.log(`User disconnected — total: ${userCount}`);
  });
});

server.listen(3000, () => {
  console.log('Chat server running at http://localhost:3000');
});
3

Build the chat UI

Create index.html with the message list, input form, and Socket.io client.

<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Chat App</title>
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body { font-family: 'Segoe UI', sans-serif; background: #0f172a; color: #fff; height: 100vh; display: flex; flex-direction: column; }
    .header { background: #1e293b; padding: 16px 20px; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #334155; }
    .header h1 { font-size: 1.1rem; }
    .user-count { font-size: 0.8rem; color: #22c55e; }
    .messages { flex: 1; overflow-y: auto; padding: 20px; display: flex; flex-direction: column; gap: 10px; }
    .msg { max-width: 70%; padding: 10px 14px; border-radius: 12px; font-size: 0.9rem; line-height: 1.4; }
    .msg.mine  { background: #1E3A6E; align-self: flex-end; border-bottom-right-radius: 4px; }
    .msg.theirs{ background: #1e293b; align-self: flex-start; border-bottom-left-radius: 4px; }
    .msg .name { font-size: 0.72rem; font-weight: 700; color: #F59C0D; margin-bottom: 3px; }
    .msg.system{ background: transparent; color: #64748b; font-size: 0.78rem; text-align: center; align-self: center; font-style: italic; }
    .typing-indicator { padding: 0 20px 8px; font-size: 0.78rem; color: #64748b; font-style: italic; min-height: 22px; }
    .input-area { padding: 16px 20px; background: #1e293b; border-top: 1px solid #334155; display: flex; gap: 10px; }
    .input-area input { flex: 1; padding: 10px 16px; border-radius: 24px; border: 1px solid #334155; background: #0f172a; color: #fff; font-size: 0.9rem; }
    .input-area button { padding: 10px 20px; background: #F59C0D; border: none; border-radius: 24px; color: #fff; font-weight: 700; cursor: pointer; }
    .username-screen { position: fixed; inset: 0; background: #0f172a; display: flex; align-items: center; justify-content: center; flex-direction: column; gap: 16px; z-index: 10; }
    .username-screen h2 { font-size: 1.4rem; }
    .username-screen input { padding: 12px 20px; border-radius: 10px; border: 1px solid #334155; background: #1e293b; color: #fff; font-size: 1rem; width: 280px; text-align: center; }
    .username-screen button { padding: 12px 32px; background: #F59C0D; border: none; border-radius: 10px; color: #fff; font-weight: 700; font-size: 1rem; cursor: pointer; }
  </style>
</head>
<body>

<?php require_once __DIR__ . '/../promo_banner.php'; ?>

  <div class="username-screen" id="usernameScreen">
    <h2>💬 Join Chat</h2>
    <input type="text" id="usernameInput" placeholder="Your name..." maxlength="20" />
    <button onclick="joinChat()">Join</button>
  </div>

  <div class="header">
    <h1>💬 Chat Room</h1>
    <span class="user-count" id="userCount">1 online</span>
  </div>
  <div class="messages" id="messages"></div>
  <div class="typing-indicator" id="typingIndicator"></div>
  <div class="input-area">
    <input type="text" id="msgInput" placeholder="Type a message..." autocomplete="off" />
    <button onclick="sendMessage()">Send</button>
  </div>

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

Write the client JavaScript

Connect to the server via Socket.io and handle sending, receiving, and typing events.

// client.js
const socket = io();
let username = '';
let typingTimer;

function joinChat() {
  const input = document.getElementById('usernameInput').value.trim();
  if (!input) return;
  username = input;
  document.getElementById('usernameScreen').style.display = 'none';
  document.getElementById('msgInput').focus();
}

function sendMessage() {
  const input = document.getElementById('msgInput');
  const text  = input.value.trim();
  if (!text || !username) return;

  socket.emit('chat-message', { username, text, time: new Date().toLocaleTimeString() });
  socket.emit('stop-typing');
  input.value = '';
}

// Send on Enter key
document.getElementById('msgInput').addEventListener('keypress', (e) => {
  if (e.key === 'Enter') sendMessage();
});

// Typing indicator
document.getElementById('msgInput').addEventListener('input', () => {
  if (!username) return;
  socket.emit('typing', username);
  clearTimeout(typingTimer);
  typingTimer = setTimeout(() => socket.emit('stop-typing'), 1500);
});

// Receive messages
socket.on('chat-message', ({ username: sender, text, time }) => {
  const messages = document.getElementById('messages');
  const div = document.createElement('div');
  div.className = `msg ${sender === username ? 'mine' : 'theirs'}`;
  div.innerHTML = `<div class="name">${sender} · ${time}</div>${text}`;
  messages.appendChild(div);
  messages.scrollTop = messages.scrollHeight;
});

// System messages (join/leave)
socket.on('system-message', (text) => {
  const messages = document.getElementById('messages');
  const div = document.createElement('div');
  div.className = 'msg system';
  div.textContent = text;
  messages.appendChild(div);
  messages.scrollTop = messages.scrollHeight;
});

// User count
socket.on('user-count', (count) => {
  document.getElementById('userCount').textContent = `${count} online`;
});

// Typing indicators
socket.on('typing', (name) => {
  document.getElementById('typingIndicator').textContent = `${name} is typing...`;
});
socket.on('stop-typing', () => {
  document.getElementById('typingIndicator').textContent = '';
});
# Start the server:
node server.js

# Output:
Chat server running at http://localhost:3000

# Open http://localhost:3000 in two browser tabs
# Enter different usernames in each tab
# Type in one tab — messages appear instantly in the other
💡 Tip: To deploy this online for free, push to GitHub and deploy to Railway (railway.app) — it detects Node.js automatically and gives you a public URL. Free tier is enough for a personal project.

🎉 You Did It!

You built a real-time bidirectional app — the hardest concept in web development — in under 50 lines per file. Socket.io handles reconnections, fallbacks, and rooms automatically. Add io.to(roomName).emit() to create separate chat rooms.

Found something wrong?

Spotted a bug, broken code, or something that doesn't look right? Tell us what's off and we'll fix it.