🎯 Learning Objectives
By the end of this lesson, you will be able to:
- Build a real-time chat application
- Implement WebSocket connections
- Handle user authentication and authorization
- Manage message history and persistence
- Implement file sharing capabilities
- Create responsive chat interface
📚 Project Overview
We'll build a real-time chat application that includes: - WebSocket integration for real-time messaging - User authentication and authorization - Message history and persistence - File sharing (images and documents) - Responsive design for mobile and desktop - Online user status and typing indicators
🏗️ Project Structure
chat-app/
├── package.json
├── server/
│ ├── server.js
│ ├── models/
│ │ ├── User.js
│ │ └── Message.js
│ ├── routes/
│ │ ├── auth.js
│ │ └── messages.js
│ └── middleware/
│ └── auth.js
├── client/
│ ├── package.json
│ ├── src/
│ │ ├── App.js
│ │ ├── components/
│ │ │ ├── Login.js
│ │ │ ├── ChatRoom.js
│ │ │ ├── MessageList.js
│ │ │ ├── MessageInput.js
│ │ │ └── UserList.js
│ │ └── hooks/
│ │ └── useSocket.js
└── README.md
💻 Complete Implementation
server/server.js
const express = require('express');
const http = require('http');
const socketIo = require('socket.io');
const mongoose = require('mongoose');
const cors = require('cors');
const authRoutes = require('./routes/auth');
const messageRoutes = require('./routes/messages');
const authMiddleware = require('./middleware/auth');
const app = express();
const server = http.createServer(app);
const io = socketIo(server, {
cors: {
origin: "http://localhost:3000",
methods: ["GET", "POST"]
}
});
// Middleware
app.use(cors());
app.use(express.json());
// Routes
app.use('/api/auth', authRoutes);
app.use('/api/messages', authMiddleware, messageRoutes);
// Socket.io connection handling
io.use((socket, next) => {
const token = socket.handshake.auth.token;
if (!token) {
return next(new Error('Authentication error'));
}
// Verify JWT token
const user = jwt.verify(token, process.env.JWT_SECRET);
socket.userId = user.id;
socket.username = user.username;
next();
});
io.on('connection', (socket) => {
console.log(`User ${socket.username} connected`);
// Join user to general room
socket.join('general');
// Broadcast user online status
socket.broadcast.emit('userOnline', {
userId: socket.userId,
username: socket.username
});
// Handle new message
socket.on('sendMessage', async (data) => {
try {
const message = new Message({
content: data.content,
sender: socket.userId,
room: data.room || 'general',
type: data.type || 'text'
});
await message.save();
// Broadcast message to room
io.to(data.room || 'general').emit('newMessage', {
id: message._id,
content: message.content,
sender: {
id: socket.userId,
username: socket.username
},
timestamp: message.timestamp,
type: message.type
});
} catch (error) {
socket.emit('error', { message: 'Failed to send message' });
}
});
// Handle typing indicator
socket.on('typing', (data) => {
socket.broadcast.to(data.room || 'general').emit('userTyping', {
userId: socket.userId,
username: socket.username,
isTyping: data.isTyping
});
});
// Handle file upload
socket.on('fileUpload', async (data) => {
try {
const message = new Message({
content: data.filename,
sender: socket.userId,
room: data.room || 'general',
type: 'file',
fileData: {
filename: data.filename,
mimetype: data.mimetype,
size: data.size
}
});
await message.save();
io.to(data.room || 'general').emit('newMessage', {
id: message._id,
content: message.content,
sender: {
id: socket.userId,
username: socket.username
},
timestamp: message.timestamp,
type: message.type,
fileData: message.fileData
});
} catch (error) {
socket.emit('error', { message: 'Failed to upload file' });
}
});
// Handle disconnect
socket.on('disconnect', () => {
console.log(`User ${socket.username} disconnected`);
socket.broadcast.emit('userOffline', {
userId: socket.userId,
username: socket.username
});
});
});
// Connect to MongoDB
mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost:27017/chat-app')
.then(() => console.log('Connected to MongoDB'))
.catch(err => console.error('MongoDB connection error:', err));
const PORT = process.env.PORT || 5000;
server.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
client/src/hooks/useSocket.js
import { useEffect, useState } from 'react';
import io from 'socket.io-client';
export function useSocket(token) {
const [socket, setSocket] = useState(null);
const [connected, setConnected] = useState(false);
useEffect(() => {
if (!token) return;
const newSocket = io('http://localhost:5000', {
auth: { token }
});
newSocket.on('connect', () => {
setConnected(true);
console.log('Connected to server');
});
newSocket.on('disconnect', () => {
setConnected(false);
console.log('Disconnected from server');
});
setSocket(newSocket);
return () => {
newSocket.close();
};
}, [token]);
return { socket, connected };
}
client/src/components/ChatRoom.js
import React, { useState, useEffect, useRef } from 'react';
import MessageList from './MessageList';
import MessageInput from './MessageInput';
import UserList from './UserList';
import { useSocket } from '../hooks/useSocket';
function ChatRoom({ user, token }) {
const { socket, connected } = useSocket(token);
const [messages, setMessages] = useState([]);
const [users, setUsers] = useState([]);
const [typingUsers, setTypingUsers] = useState([]);
const messagesEndRef = useRef(null);
useEffect(() => {
if (!socket) return;
// Listen for new messages
socket.on('newMessage', (message) => {
setMessages(prev => [...prev, message]);
});
// Listen for user online/offline
socket.on('userOnline', (userData) => {
setUsers(prev => [...prev, userData]);
});
socket.on('userOffline', (userData) => {
setUsers(prev => prev.filter(u => u.userId !== userData.userId));
});
// Listen for typing indicators
socket.on('userTyping', (data) => {
if (data.isTyping) {
setTypingUsers(prev => [...prev.filter(u => u.userId !== data.userId), data]);
} else {
setTypingUsers(prev => prev.filter(u => u.userId !== data.userId));
}
});
// Load message history
fetchMessages();
return () => {
socket.off('newMessage');
socket.off('userOnline');
socket.off('userOffline');
socket.off('userTyping');
};
}, [socket]);
useEffect(() => {
scrollToBottom();
}, [messages]);
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
};
const fetchMessages = async () => {
try {
const response = await fetch('/api/messages', {
headers: {
'Authorization': `Bearer ${token}`
}
});
const data = await response.json();
setMessages(data);
} catch (error) {
console.error('Error fetching messages:', error);
}
};
const sendMessage = (content, type = 'text') => {
if (socket && content.trim()) {
socket.emit('sendMessage', {
content: content.trim(),
type,
room: 'general'
});
}
};
const handleTyping = (isTyping) => {
if (socket) {
socket.emit('typing', {
isTyping,
room: 'general'
});
}
};
const handleFileUpload = (file) => {
if (socket && file) {
const reader = new FileReader();
reader.onload = (e) => {
socket.emit('fileUpload', {
filename: file.name,
mimetype: file.type,
size: file.size,
data: e.target.result,
room: 'general'
});
};
reader.readAsDataURL(file);
}
};
return (
<div className="chat-room">
<div className="chat-header">
<h2>Chat Room</h2>
<div className={`connection-status ${connected ? 'connected' : 'disconnected'}`}>
{connected ? 'Connected' : 'Disconnected'}
</div>
</div>
<div className="chat-content">
<div className="messages-container">
<MessageList
messages={messages}
currentUser={user}
/>
<div ref={messagesEndRef} />
{typingUsers.length > 0 && (
<div className="typing-indicator">
{typingUsers.map(user => user.username).join(', ')}
{typingUsers.length === 1 ? ' is' : ' are'} typing...
</div>
)}
</div>
<div className="users-sidebar">
<UserList users={users} currentUser={user} />
</div>
</div>
<MessageInput
onSendMessage={sendMessage}
onTyping={handleTyping}
onFileUpload={handleFileUpload}
/>
</div>
);
}
export default ChatRoom;
🎯 Key Features Implemented
- Real-Time Messaging - WebSocket integration
- User Authentication - JWT-based authentication
- Message History - Persistent chat data
- File Sharing - Image and document uploads
- Typing Indicators - Real-time typing status
- Online Status - User presence tracking
- Responsive Design - Mobile and desktop support
🎯 Key Takeaways
- WebSockets enable real-time communication
- Authentication secures chat sessions
- Message persistence stores chat history
- File handling supports media sharing
- User experience includes typing indicators
🚀 Next Steps
Congratulations! You've built a real-time chat application. In the next lesson, we'll create:
- E-Commerce Shopping Cart - Product catalog and checkout
- Payment integration - Simulated payment processing
- Order management - User accounts and order history
- Inventory tracking - Product availability
Next Lesson: Lesson 8: Capstone Project - E-Commerce Cart