On this page
Real-Time Chat
Build a live chat room where messages appear instantly for all connected users. Practice WebSockets, event-driven architecture, and a React frontend.
Requirements
Features
- Join a chat room with a username
- Send and receive messages in real time
- Display online user count
- Show message timestamps
- Auto-scroll to the latest message
Step 1: Project Structure
chat-app/
├── server/
│ ├── package.json
│ └── index.js
└── client/
├── package.json
└── src/
└── App.jsx
Step 2: Socket.io Server
cd server && npm init -y && npm install express socket.io cors
server/index.js
import express from 'express';
import { createServer } from 'http';
import { Server } from 'socket.io';
import cors from 'cors';
const app = express();
app.use(cors());
const httpServer = createServer(app);
const io = new Server(httpServer, { cors: { origin: 'http://localhost:5173' } });
const users = new Map();
io.on('connection', socket => {
console.log(`Connected: ${socket.id}`);
io.emit('user-count', users.size);
socket.on('join', username => {
users.set(socket.id, username);
io.emit('user-joined', { username, time: new Date().toISOString() });
io.emit('user-count', users.size);
});
socket.on('message', text => {
const username = users.get(socket.id);
if (!username || !text.trim()) return;
io.emit('message', {
username,
text: text.trim(),
time: new Date().toISOString(),
});
});
socket.on('disconnect', () => {
const username = users.get(socket.id);
if (username) {
users.delete(socket.id);
io.emit('user-left', { username, time: new Date().toISOString() });
io.emit('user-count', users.size);
}
});
});
httpServer.listen(3001, () => console.log('Server on http://localhost:3001'));
Add "type": "module" to server/package.json.
Step 3: React Client
cd client && npm create vite@latest . -- --template react && npm install socket.io-client
client/src/App.jsx
import { useState, useEffect, useRef } from 'react';
import { io } from 'socket.io-client';
const socket = io('http://localhost:3001');
export default function App() {
const [username, setUsername] = useState('');
const [joined, setJoined] = useState(false);
const [messages, setMessages] = useState([]);
const [input, setInput] = useState('');
const [userCount, setUserCount] = useState(0);
const bottomRef = useRef(null);
useEffect(() => {
socket.on('message', msg => setMessages(prev => [...prev, msg]));
socket.on('user-joined', msg => setMessages(prev => [...prev, { ...msg, system: true }]));
socket.on('user-left', msg => setMessages(prev => [...prev, { ...msg, system: true }]));
socket.on('user-count', count => setUserCount(count));
return () => { socket.off(); };
}, []);
useEffect(() => {
bottomRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [messages]);
function handleJoin(e) {
e.preventDefault();
if (!username.trim()) return;
socket.emit('join', username.trim());
setJoined(true);
}
function sendMessage(e) {
e.preventDefault();
if (!input.trim()) return;
socket.emit('message', input);
setInput('');
}
if (!joined) {
return (
<form onSubmit={handleJoin}>
<h1>Join Chat</h1>
<input value={username} onChange={e => setUsername(e.target.value)} placeholder="Your name" required />
<button type="submit">Enter</button>
</form>
);
}
return (
<div className="chat">
<header><h1>Chat Room</h1><span>{userCount} online</span></header>
<div className="messages">
{messages.map((msg, i) => (
<div key={i} className={msg.system ? 'system' : 'message'}>
{msg.system ? <em>{msg.username} joined</em> : <><strong>{msg.username}</strong>: {msg.text}</>}
<small>{new Date(msg.time).toLocaleTimeString()}</small>
</div>
))}
<div ref={bottomRef} />
</div>
<form onSubmit={sendMessage} className="input-bar">
<input value={input} onChange={e => setInput(e.target.value)} placeholder="Type a message..." />
<button type="submit">Send</button>
</form>
</div>
);
}
Add CSS for .chat, .messages, .input-bar, and .system to style the layout. Run the server (node index.js) and client (npm run dev) in separate terminals, then open two browser tabs to test.
Extension Ideas
- Multiple rooms — let users create or join named channels
- Private messages — direct messaging between two users
- Message history — persist messages in Redis or MongoDB
- Typing indicators — emit
typingevents while the user composes - File sharing — upload images via a REST endpoint and share URLs
- Authentication — require login before joining chat rooms