Real-time Chat Applications
Build a complete real-time chat application with user authentication, message persistence, and advanced features. This is a foundational concept in server-side JavaScript development that professional developers rely on daily. The explanations below are written to be beginner-friendly while covering the depth and nuance that comes from real-world Node.js experience. Take your time with each section and practice the examples
90 min•By Priygop Team•Last updated: Feb 2026
Chat Application Architecture
A real-time chat application requires careful consideration of user authentication, message persistence, room management, and real-time updates. We'll build a complete chat system with MongoDB for message storage.
Chat Server Implementation
Example
// Chat server with Socket.io and MongoDB
const express = require('express');
const http = require('http');
const socketIo = require('socket.io');
const mongoose = require('mongoose');
const jwt = require('jsonwebtoken');
const app = express();
const server = http.createServer(app);
const io = socketIo(server, {
cors: { origin: "http://localhost:3000" }
});
// MongoDB connection
mongoose.connect('mongodb://localhost:27017/chatapp');
// Message Schema
const messageSchema = new mongoose.Schema({
room: String,
userId: String,
username: String,
message: String,
timestamp: { type: Date, default: Date.now }
});
const Message = mongoose.model('Message', messageSchema);
// User Schema
const userSchema = new mongoose.Schema({
username: String,
email: String,
password: String,
isOnline: { type: Boolean, default: false },
lastSeen: { type: Date, default: Date.now }
});
const User = mongoose.model('User', userSchema);
// Socket.io connection handling
io.on('connection', async (socket) => {
console.log('User connected:', socket.id);
// Authenticate user
socket.on('authenticate', async (token) => {
try {
const decoded = jwt.verify(token, 'your-secret-key');
const user = await User.findById(decoded.userId);
if (user) {
socket.userId = user._id;
socket.username = user.username;
// Update user status
await User.findByIdAndUpdate(user._id, {
isOnline: true,
lastSeen: new Date()
});
socket.emit('authenticated', {
userId: user._id,
username: user.username
});
// Notify other users
socket.broadcast.emit('user-online', {
userId: user._id,
username: user.username
});
}
} catch (error) {
socket.emit('auth-error', 'Invalid token');
}
});
// Join a chat room
socket.on('join-room', async (roomName) => {
socket.join(roomName);
// Load previous messages
const messages = await Message.find({ room: roomName })
.sort({ timestamp: 1 })
.limit(50);
socket.emit('room-messages', messages);
socket.to(roomName).emit('user-joined', {
userId: socket.userId,
username: socket.username,
message: socket.username + ' joined the room'
});
});
// Send message
socket.on('send-message', async (data) => {
const newMessage = new Message({
room: data.room,
userId: socket.userId,
username: socket.username,
message: data.message
});
await newMessage.save();
// Broadcast to room
socket.to(data.room).emit('new-message', {
userId: socket.userId,
username: socket.username,
message: data.message,
timestamp: newMessage.timestamp
});
});
// Typing indicators
socket.on('typing', (data) => {
socket.to(data.room).emit('user-typing', {
userId: socket.userId,
username: socket.username
});
});
socket.on('stop-typing', (data) => {
socket.to(data.room).emit('user-stop-typing', {
userId: socket.userId,
username: socket.username
});
});
// Handle disconnection
socket.on('disconnect', async () => {
if (socket.userId) {
await User.findByIdAndUpdate(socket.userId, {
isOnline: false,
lastSeen: new Date()
});
socket.broadcast.emit('user-offline', {
userId: socket.userId,
username: socket.username
});
}
console.log('User disconnected:', socket.id);
});
});
// API routes for user management
app.get('/api/users', async (req, res) => {
try {
const users = await User.find({}, 'username isOnline lastSeen');
res.json(users);
} catch (error) {
res.status(500).json({ error: 'Failed to fetch users' });
}
});
app.get('/api/messages/:room', async (req, res) => {
try {
const messages = await Message.find({ room: req.params.room })
.sort({ timestamp: -1 })
.limit(100);
res.json(messages.reverse());
} catch (error) {
res.status(500).json({ error: 'Failed to fetch messages' });
}
});
server.listen(3001, () => {
console.log('Chat server running on port 3001');
});Chat Client Implementation
Example
// React chat client component
import React, { useState, useEffect, useRef } from 'react';
import { io } from 'socket.io-client';
const ChatRoom = ({ roomName, user }) => {
const [messages, setMessages] = useState([]);
const [newMessage, setNewMessage] = useState('');
const [isTyping, setIsTyping] = useState(false);
const [typingUsers, setTypingUsers] = useState([]);
const [socket, setSocket] = useState(null);
const messagesEndRef = useRef(null);
useEffect(() => {
const newSocket = io('http://localhost:3001');
setSocket(newSocket);
// Authenticate
newSocket.emit('authenticate', localStorage.getItem('token'));
newSocket.on('authenticated', (data) => {
console.log('Authenticated:', data);
newSocket.emit('join-room', roomName);
});
newSocket.on('room-messages', (roomMessages) => {
setMessages(roomMessages);
});
newSocket.on('new-message', (message) => {
setMessages(prev => [...prev, message]);
});
newSocket.on('user-typing', (data) => {
setTypingUsers(prev => [...prev.filter(u => u.userId !== data.userId), data]);
});
newSocket.on('user-stop-typing', (data) => {
setTypingUsers(prev => prev.filter(u => u.userId !== data.userId));
});
return () => newSocket.close();
}, [roomName]);
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [messages]);
const sendMessage = () => {
if (newMessage.trim() && socket) {
socket.emit('send-message', {
room: roomName,
message: newMessage
});
setNewMessage('');
}
};
const handleTyping = () => {
if (!isTyping) {
setIsTyping(true);
socket.emit('typing', { room: roomName });
}
};
const handleStopTyping = () => {
setIsTyping(false);
socket.emit('stop-typing', { room: roomName });
};
return (
<div className="chat-room">
<div className="messages">
{messages.map((msg, index) => (
<div key={index} className={`message ${msg.userId === user.id ? 'own' : 'other'}`}>
<div className="message-header">
<span className="username">{msg.username}</span>
<span className="timestamp">
{new Date(msg.timestamp).toLocaleTimeString()}
</span>
</div>
<div className="message-content">{msg.message}</div>
</div>
))}
{typingUsers.length > 0 && (
<div className="typing-indicator">
{typingUsers.map(user => user.username).join(', ')} is typing...
</div>
)}
<div ref={messagesEndRef} />
</div>
<div className="message-input">
<input
type="text"
value={newMessage}
onChange={(e) => setNewMessage(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && sendMessage()}
onKeyDown={handleTyping}
onKeyUp={handleStopTyping}
placeholder="Type a message..."
/>
<button onClick={sendMessage}>Send</button>
</div>
</div>
);
};
export default ChatRoom;