💬 Project Introduction: Real-Time Chat Application
This project is a fully functional Real-Time Chat Application that allows users to communicate instantly with each other. It features one-on-one private messaging, online/offline status indicators, typing indicators, and an admin panel to manage users and monitor conversations.
The Problem it Solves:
Modern communication requires instant messaging capabilities. This application provides a lightweight, self-hosted chat solution similar to WhatsApp Web or Facebook Messenger. It eliminates the need for third-party services and gives you complete control over your data.
Key Features:
- User System:
- User registration and login
- Profile pictures
- Online/offline status
- Last seen timestamps
- Typing indicators
- Chat Features:
- One-on-one private messaging
- Real-time message delivery (AJAX polling)
- Message read receipts (delivered/seen)
- Emoji support
- File/image sharing
- Message history
- Delete messages
- Admin Panel:
- User management (view/block/delete users)
- Monitor active chats
- View chat logs
- System statistics
- Announcement broadcast
Technology Stack:
- Frontend: HTML5, CSS3, JavaScript (Fetch API, Emoji Mart library)
- Backend: PHP (Object-Oriented PHP with PDO)
- Database: MySQL
- Real-time: AJAX polling (every 2 seconds)
- Server: Apache (XAMPP/WAMP/LAMP)
📁 Project File Structure
chat-application/ │ ├── index.php # Landing page / Login redirect ├── login.php # User login page ├── register.php # User registration page ├── logout.php # Logout script ├── chat.php # Main chat interface ├── profile.php # User profile page │ ├── admin/ # Admin Panel │ ├── index.php # Admin login │ ├── dashboard.php # Admin dashboard │ ├── users.php # Manage users │ ├── chats.php # Monitor chats │ ├── broadcast.php # Send announcements │ └── logout.php # Admin logout │ ├── includes/ # Backend logic │ ├── config.php # Database connection │ ├── functions.php # Helper functions │ ├── session.php # Session management │ └── auth.php # Authentication functions │ ├── api/ # AJAX endpoints │ ├── send_message.php # Send new message │ ├── get_messages.php # Get messages (polling) │ ├── get_users.php # Get online users │ ├── typing.php # Update typing status │ ├── mark_read.php # Mark messages as read │ └── upload_file.php # Handle file uploads │ ├── assets/ # Static assets │ ├── css/ │ │ ├── style.css # Main styles │ │ └── chat.css # Chat interface styles │ ├── js/ │ │ ├── main.js # Main JavaScript │ │ ├── chat.js # Chat functionality │ │ └── emoji.js # Emoji picker │ ├── images/ │ │ ├── avatars/ # User profile pictures │ │ └── uploads/ # Shared files/images │ └── vendor/ # Third-party libraries │ └── emoji-mart/ # Emoji picker library │ └── database/ └── chat_app.sql # Database dump
🗄️ Database Setup (database/chat_app.sql)
Create a database named chat_app and run this SQL.
-- phpMyAdmin SQL Dump -- Database: `chat_app` CREATE DATABASE IF NOT EXISTS `chat_app`; USE `chat_app`; -- -------------------------------------------------------- -- Table structure for table `users` -- -------------------------------------------------------- CREATE TABLE `users` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL, `email` varchar(100) NOT NULL, `password` varchar(255) NOT NULL, `full_name` varchar(100) NOT NULL, `avatar` varchar(255) DEFAULT 'default.png', `bio` text DEFAULT NULL, `role` enum('user','admin') DEFAULT 'user', `status` enum('online','offline','away') DEFAULT 'offline', `last_seen` timestamp NULL DEFAULT NULL, `created_at` timestamp NOT NULL DEFAULT current_timestamp(), PRIMARY KEY (`id`), UNIQUE KEY `username` (`username`), UNIQUE KEY `email` (`email`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- Sample users (passwords: password123) INSERT INTO `users` (`username`, `email`, `password`, `full_name`, `role`, `status`) VALUES ('admin', '[email protected]', '$2y$10$YourHashedPasswordHere', 'Administrator', 'admin', 'offline'), ('john', '[email protected]', '$2y$10$YourHashedPasswordHere', 'John Doe', 'user', 'offline'), ('jane', '[email protected]', '$2y$10$YourHashedPasswordHere', 'Jane Smith', 'user', 'offline'); -- -------------------------------------------------------- -- Table structure for table `messages` -- -------------------------------------------------------- CREATE TABLE `messages` ( `id` int(11) NOT NULL AUTO_INCREMENT, `sender_id` int(11) NOT NULL, `receiver_id` int(11) NOT NULL, `message` text DEFAULT NULL, `file_path` varchar(255) DEFAULT NULL, `file_type` varchar(50) DEFAULT NULL, `is_read` tinyint(1) DEFAULT 0, `read_at` timestamp NULL DEFAULT NULL, `deleted_by_sender` tinyint(1) DEFAULT 0, `deleted_by_receiver` tinyint(1) DEFAULT 0, `created_at` timestamp NOT NULL DEFAULT current_timestamp(), PRIMARY KEY (`id`), KEY `sender_id` (`sender_id`), KEY `receiver_id` (`receiver_id`), KEY `created_at` (`created_at`), CONSTRAINT `messages_ibfk_1` FOREIGN KEY (`sender_id`) REFERENCES `users` (`id`) ON DELETE CASCADE, CONSTRAINT `messages_ibfk_2` FOREIGN KEY (`receiver_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- Sample messages INSERT INTO `messages` (`sender_id`, `receiver_id`, `message`, `is_read`, `created_at`) VALUES (2, 3, 'Hey Jane, how are you?', 1, DATE_SUB(NOW(), INTERVAL 10 MINUTE)), (3, 2, 'Hi John! I\'m good, thanks!', 1, DATE_SUB(NOW(), INTERVAL 9 MINUTE)), (2, 3, 'Want to discuss the project?', 0, DATE_SUB(NOW(), INTERVAL 5 MINUTE)); -- -------------------------------------------------------- -- Table structure for table `typing_status` -- -------------------------------------------------------- CREATE TABLE `typing_status` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` int(11) NOT NULL, `chatting_with` int(11) NOT NULL, `is_typing` tinyint(1) DEFAULT 0, `updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(), PRIMARY KEY (`id`), UNIQUE KEY `unique_conversation` (`user_id`,`chatting_with`), KEY `user_id` (`user_id`), KEY `chatting_with` (`chatting_with`), CONSTRAINT `typing_status_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE, CONSTRAINT `typing_status_ibfk_2` FOREIGN KEY (`chatting_with`) REFERENCES `users` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- -------------------------------------------------------- -- Table structure for table `blocked_users` -- -------------------------------------------------------- CREATE TABLE `blocked_users` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` int(11) NOT NULL, `blocked_user_id` int(11) NOT NULL, `created_at` timestamp NOT NULL DEFAULT current_timestamp(), PRIMARY KEY (`id`), UNIQUE KEY `unique_block` (`user_id`,`blocked_user_id`), KEY `user_id` (`user_id`), KEY `blocked_user_id` (`blocked_user_id`), CONSTRAINT `blocked_users_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE, CONSTRAINT `blocked_users_ibfk_2` FOREIGN KEY (`blocked_user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- -------------------------------------------------------- -- Table structure for table `announcements` -- -------------------------------------------------------- CREATE TABLE `announcements` ( `id` int(11) NOT NULL AUTO_INCREMENT, `title` varchar(200) NOT NULL, `message` text NOT NULL, `created_by` int(11) NOT NULL, `created_at` timestamp NOT NULL DEFAULT current_timestamp(), PRIMARY KEY (`id`), KEY `created_by` (`created_by`), CONSTRAINT `announcements_ibfk_1` FOREIGN KEY (`created_by`) REFERENCES `users` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; COMMIT; 💻 Core PHP Files
1. Database Configuration (includes/config.php)
<?php // Database configuration define('DB_HOST', 'localhost'); define('DB_NAME', 'chat_app'); define('DB_USER', 'root'); define('DB_PASS', ''); try { $pdo = new PDO( "mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=utf8mb4", DB_USER, DB_PASS, [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES => false ] ); } catch (PDOException $e) { die("Connection failed: " . $e->getMessage()); } // Start session if not started if (session_status() == PHP_SESSION_NONE) { session_start(); } // Site configuration define('SITE_NAME', 'Chat Application'); define('SITE_URL', 'http://localhost/chat-application/'); define('UPLOAD_PATH', __DIR__ . '/../assets/uploads/'); define('AVATAR_PATH', __DIR__ . '/../assets/images/avatars/'); // Helper functions function sanitize($data) { $data = trim($data); $data = stripslashes($data); $data = htmlspecialchars($data); return $data; } function redirect($url) { header("Location: $url"); exit; } function isLoggedIn() { return isset($_SESSION['user_id']); } function isAdmin() { return isset($_SESSION['user_role']) && $_SESSION['user_role'] === 'admin'; } function getUserById($id) { global $pdo; $stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?"); $stmt->execute([$id]); return $stmt->fetch(); } function timeAgo($timestamp) { $time_ago = strtotime($timestamp); $current_time = time(); $time_difference = $current_time - $time_ago; $seconds = $time_difference; $minutes = round($seconds / 60); $hours = round($seconds / 3600); $days = round($seconds / 86400); $weeks = round($seconds / 604800); $months = round($seconds / 2629440); $years = round($seconds / 31553280); if ($seconds <= 60) { return "Just now"; } else if ($minutes <= 60) { return ($minutes == 1) ? "1 minute ago" : "$minutes minutes ago"; } else if ($hours <= 24) { return ($hours == 1) ? "1 hour ago" : "$hours hours ago"; } else if ($days <= 7) { return ($days == 1) ? "yesterday" : "$days days ago"; } else if ($weeks <= 4.3) { return ($weeks == 1) ? "1 week ago" : "$weeks weeks ago"; } else if ($months <= 12) { return ($months == 1) ? "1 month ago" : "$months months ago"; } else { return ($years == 1) ? "1 year ago" : "$years years ago"; } } function updateUserStatus($user_id, $status) { global $pdo; $stmt = $pdo->prepare("UPDATE users SET status = ?, last_seen = NOW() WHERE id = ?"); return $stmt->execute([$status, $user_id]); } ?> 2. Authentication Functions (includes/auth.php)
<?php require_once 'config.php'; class Auth { public static function login($username, $password) { global $pdo; $stmt = $pdo->prepare("SELECT * FROM users WHERE username = ? OR email = ?"); $stmt->execute([$username, $username]); $user = $stmt->fetch(); if ($user && password_verify($password, $user['password'])) { $_SESSION['user_id'] = $user['id']; $_SESSION['user_name'] = $user['full_name']; $_SESSION['user_username'] = $user['username']; $_SESSION['user_role'] = $user['role']; $_SESSION['user_avatar'] = $user['avatar']; // Update status to online updateUserStatus($user['id'], 'online'); return true; } return false; } public static function register($data) { global $pdo; // Check if username exists $stmt = $pdo->prepare("SELECT id FROM users WHERE username = ?"); $stmt->execute([$data['username']]); if ($stmt->fetch()) { return ['success' => false, 'message' => 'Username already taken']; } // Check if email exists $stmt = $pdo->prepare("SELECT id FROM users WHERE email = ?"); $stmt->execute([$data['email']]); if ($stmt->fetch()) { return ['success' => false, 'message' => 'Email already registered']; } // Hash password $hashedPassword = password_hash($data['password'], PASSWORD_DEFAULT); // Insert user $stmt = $pdo->prepare(" INSERT INTO users (username, email, password, full_name, bio, avatar) VALUES (?, ?, ?, ?, ?, ?) "); $avatar = 'default.png'; $success = $stmt->execute([ $data['username'], $data['email'], $hashedPassword, $data['full_name'], $data['bio'] ?? null, $avatar ]); if ($success) { return ['success' => true, 'message' => 'Registration successful! Please login.']; } return ['success' => false, 'message' => 'Registration failed. Please try again.']; } public static function logout() { if (isset($_SESSION['user_id'])) { updateUserStatus($_SESSION['user_id'], 'offline'); } session_destroy(); redirect('login.php'); } public static function checkLogin() { if (!isset($_SESSION['user_id'])) { redirect('login.php'); } } public static function checkAdmin() { self::checkLogin(); if ($_SESSION['user_role'] !== 'admin') { redirect('chat.php'); } } public static function getCurrentUser() { global $pdo; if (!isset($_SESSION['user_id'])) { return null; } $stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?"); $stmt->execute([$_SESSION['user_id']]); return $stmt->fetch(); } } ?> 3. Login Page (login.php)
<?php require_once 'includes/config.php'; require_once 'includes/auth.php'; // If already logged in, redirect to chat if (isLoggedIn()) { redirect('chat.php'); } $error = ''; if ($_SERVER['REQUEST_METHOD'] === 'POST') { $username = $_POST['username'] ?? ''; $password = $_POST['password'] ?? ''; if (Auth::login($username, $password)) { redirect('chat.php'); } else { $error = 'Invalid username/email or password'; } } ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Login - <?php echo SITE_NAME; ?></title> <link rel="stylesheet" href="assets/css/style.css"> </head> <body class="auth-page"> <div class="auth-container"> <div class="auth-card"> <div class="auth-header"> <h1><?php echo SITE_NAME; ?></h1> <p>Login to start chatting</p> </div> <?php if ($error): ?> <div class="alert alert-error"><?php echo $error; ?></div> <?php endif; ?> <form method="POST" action="" class="auth-form"> <div class="form-group"> <label for="username">Username or Email</label> <input type="text" id="username" name="username" required placeholder="Enter your username or email"> </div> <div class="form-group"> <label for="password">Password</label> <input type="password" id="password" name="password" required placeholder="Enter your password"> </div> <button type="submit" class="btn btn-primary btn-block">Login</button> </form> <div class="auth-footer"> <p>Don't have an account? <a href="register.php">Register</a></p> </div> <div class="demo-credentials"> <p><strong>Demo Credentials:</strong></p> <p>Admin: admin / password123</p> <p>User1: john / password123</p> <p>User2: jane / password123</p> </div> </div> </div> </body> </html> 4. Registration Page (register.php)
<?php require_once 'includes/config.php'; require_once 'includes/auth.php'; // If already logged in, redirect to chat if (isLoggedIn()) { redirect('chat.php'); } $message = ''; $error = ''; if ($_SERVER['REQUEST_METHOD'] === 'POST') { $data = [ 'username' => sanitize($_POST['username']), 'email' => sanitize($_POST['email']), 'password' => $_POST['password'], 'confirm_password' => $_POST['confirm_password'], 'full_name' => sanitize($_POST['full_name']), 'bio' => sanitize($_POST['bio'] ?? '') ]; // Validation if (strlen($data['password']) < 6) { $error = 'Password must be at least 6 characters'; } elseif ($data['password'] !== $data['confirm_password']) { $error = 'Passwords do not match'; } elseif (!filter_var($data['email'], FILTER_VALIDATE_EMAIL)) { $error = 'Invalid email format'; } else { $result = Auth::register($data); if ($result['success']) { $message = $result['message']; } else { $error = $result['message']; } } } ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Register - <?php echo SITE_NAME; ?></title> <link rel="stylesheet" href="assets/css/style.css"> </head> <body class="auth-page"> <div class="auth-container"> <div class="auth-card"> <div class="auth-header"> <h1>Create Account</h1> <p>Join our chat community</p> </div> <?php if ($message): ?> <div class="alert alert-success"><?php echo $message; ?></div> <?php endif; ?> <?php if ($error): ?> <div class="alert alert-error"><?php echo $error; ?></div> <?php endif; ?> <form method="POST" action="" class="auth-form"> <div class="form-group"> <label for="full_name">Full Name</label> <input type="text" id="full_name" name="full_name" required placeholder="Enter your full name"> </div> <div class="form-group"> <label for="username">Username</label> <input type="text" id="username" name="username" required placeholder="Choose a username"> </div> <div class="form-group"> <label for="email">Email</label> <input type="email" id="email" name="email" required placeholder="Enter your email"> </div> <div class="form-row"> <div class="form-group"> <label for="password">Password</label> <input type="password" id="password" name="password" required placeholder="Min. 6 characters"> </div> <div class="form-group"> <label for="confirm_password">Confirm Password</label> <input type="password" id="confirm_password" name="confirm_password" required placeholder="Re-enter password"> </div> </div> <div class="form-group"> <label for="bio">Bio (Optional)</label> <textarea id="bio" name="bio" rows="3" placeholder="Tell something about yourself"></textarea> </div> <button type="submit" class="btn btn-primary btn-block">Register</button> </form> <div class="auth-footer"> <p>Already have an account? <a href="login.php">Login</a></p> </div> </div> </div> </body> </html> 5. Main Chat Interface (chat.php)
<?php require_once 'includes/config.php'; require_once 'includes/auth.php'; Auth::checkLogin(); $current_user = Auth::getCurrentUser(); $user_id = $_SESSION['user_id']; // Get all users except current user $users = $pdo->prepare(" SELECT u.*, (SELECT COUNT(*) FROM messages WHERE sender_id = u.id AND receiver_id = ? AND is_read = 0) as unread_count FROM users u WHERE u.id != ? ORDER BY u.status = 'online' DESC, u.last_seen DESC "); $users->execute([$user_id, $user_id]); $all_users = $users->fetchAll(); // Get selected chat partner $chat_with = isset($_GET['user']) ? (int)$_GET['user'] : ($all_users[0]['id'] ?? 0); // Mark messages as read when opening chat if ($chat_with > 0) { $pdo->prepare(" UPDATE messages SET is_read = 1, read_at = NOW() WHERE sender_id = ? AND receiver_id = ? AND is_read = 0 ")->execute([$chat_with, $user_id]); } ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Chat - <?php echo SITE_NAME; ?></title> <link rel="stylesheet" href="assets/css/chat.css"> <link rel="stylesheet" href="assets/vendor/emoji-mart/emoji-mart.css"> </head> <body> <div class="chat-app"> <!-- Sidebar - Users List --> <aside class="sidebar"> <div class="sidebar-header"> <div class="user-profile"> <img src="assets/images/avatars/<?php echo $current_user['avatar']; ?>" alt="<?php echo $current_user['full_name']; ?>" class="avatar"> <div class="user-info"> <h3><?php echo $current_user['full_name']; ?></h3> <span class="status online">Online</span> </div> </div> <div class="sidebar-actions"> <a href="profile.php" class="btn-icon" title="Profile">👤</a> <?php if (isAdmin()): ?> <a href="admin/dashboard.php" class="btn-icon" title="Admin Panel">⚙️</a> <?php endif; ?> <a href="logout.php" class="btn-icon" title="Logout">🚪</a> </div> </div> <div class="search-box"> <input type="text" id="search-users" placeholder="Search users..."> </div> <div class="users-list" id="users-list"> <?php foreach ($all_users as $user): ?> <a href="?user=<?php echo $user['id']; ?>" class="user-item <?php echo $chat_with == $user['id'] ? 'active' : ''; ?>" data-user-id="<?php echo $user['id']; ?>"> <div class="user-avatar"> <img src="assets/images/avatars/<?php echo $user['avatar']; ?>" alt="<?php echo $user['full_name']; ?>"> <span class="status-dot <?php echo $user['status']; ?>"></span> </div> <div class="user-details"> <h4><?php echo $user['full_name']; ?></h4> <p class="last-message"> <?php if ($user['status'] == 'online'): ?> <span class="online-text">Online</span> <?php else: ?> Last seen <?php echo timeAgo($user['last_seen']); ?> <?php endif; ?> </p> </div> <?php if ($user['unread_count'] > 0): ?> <span class="unread-badge"><?php echo $user['unread_count']; ?></span> <?php endif; ?> </a> <?php endforeach; ?> </div> </aside> <!-- Main Chat Area --> <main class="chat-area"> <?php if ($chat_with > 0): $chat_partner = getUserById($chat_with); ?> <!-- Chat Header --> <div class="chat-header"> <div class="chat-user-info"> <img src="assets/images/avatars/<?php echo $chat_partner['avatar']; ?>" alt="<?php echo $chat_partner['full_name']; ?>" class="avatar"> <div> <h3><?php echo $chat_partner['full_name']; ?></h3> <span class="user-status" id="partner-status"> <?php echo $chat_partner['status'] == 'online' ? 'Online' : 'Offline'; ?> </span> </div> </div> <div class="chat-actions"> <button class="btn-icon" onclick="clearChat()" title="Clear chat">🗑️</button> <button class="btn-icon" onclick="blockUser()" title="Block user">🚫</button> </div> </div> <!-- Messages Container --> <div class="messages-container" id="messages-container"> <!-- Messages will be loaded here via AJAX --> <div class="loading">Loading messages...</div> </div> <!-- Typing Indicator --> <div class="typing-indicator" id="typing-indicator" style="display: none;"> <span><?php echo $chat_partner['full_name']; ?> is typing</span> <span class="dots"> <span>.</span><span>.</span><span>.</span> </span> </div> <!-- Message Input --> <div class="message-input"> <button class="btn-icon emoji-btn" id="emoji-btn" title="Emoji">😊</button> <input type="text" id="message-text" placeholder="Type a message..." autocomplete="off"> <button class="btn-icon attach-btn" onclick="document.getElementById('file-input').click()" title="Attach file">📎</button> <input type="file" id="file-input" style="display: none;" onchange="uploadFile()"> <button class="btn-send" onclick="sendMessage()" id="send-btn">Send</button> </div> <!-- Hidden input for receiver ID --> <input type="hidden" id="receiver-id" value="<?php echo $chat_with; ?>"> <?php else: ?> <div class="no-chat-selected"> <div class="welcome-message"> <h2>Welcome to <?php echo SITE_NAME; ?>!</h2> <p>Select a user from the sidebar to start chatting</p> </div> </div> <?php endif; ?> </main> </div> <!-- Emoji Picker Container --> <div id="emoji-picker" style="display: none;"></div> <script src="assets/js/chat.js"></script> <script src="assets/vendor/emoji-mart/emoji-mart.js"></script> </body> </html> 6. API - Send Message (api/send_message.php)
<?php require_once '../includes/config.php'; require_once '../includes/auth.php'; header('Content-Type: application/json'); if (!isLoggedIn()) { echo json_encode(['success' => false, 'message' => 'Not authenticated']); exit; } $sender_id = $_SESSION['user_id']; $receiver_id = $_POST['receiver_id'] ?? 0; $message = $_POST['message'] ?? ''; $file_path = null; $file_type = null; // Validate receiver if (!$receiver_id) { echo json_encode(['success' => false, 'message' => 'Invalid receiver']); exit; } // Check if users are blocked $stmt = $pdo->prepare(" SELECT id FROM blocked_users WHERE (user_id = ? AND blocked_user_id = ?) OR (user_id = ? AND blocked_user_id = ?) "); $stmt->execute([$sender_id, $receiver_id, $receiver_id, $sender_id]); if ($stmt->fetch()) { echo json_encode(['success' => false, 'message' => 'Cannot send message to this user']); exit; } // Handle file upload if present if (isset($_FILES['file']) && $_FILES['file']['error'] == 0) { $allowed_types = ['image/jpeg', 'image/png', 'image/gif', 'application/pdf', 'text/plain']; $max_size = 5 * 1024 * 1024; // 5MB if (!in_array($_FILES['file']['type'], $allowed_types)) { echo json_encode(['success' => false, 'message' => 'File type not allowed']); exit; } if ($_FILES['file']['size'] > $max_size) { echo json_encode(['success' => false, 'message' => 'File too large (max 5MB)']); exit; } $extension = pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION); $filename = uniqid() . '.' . $extension; $upload_path = '../assets/uploads/' . $filename; if (move_uploaded_file($_FILES['file']['tmp_name'], $upload_path)) { $file_path = 'assets/uploads/' . $filename; $file_type = $_FILES['file']['type']; } } // Insert message $stmt = $pdo->prepare(" INSERT INTO messages (sender_id, receiver_id, message, file_path, file_type, created_at) VALUES (?, ?, ?, ?, ?, NOW()) "); $success = $stmt->execute([$sender_id, $receiver_id, $message, $file_path, $file_type]); if ($success) { $message_id = $pdo->lastInsertId(); // Get the inserted message $stmt = $pdo->prepare(" SELECT m.*, u.full_name as sender_name, u.avatar as sender_avatar FROM messages m JOIN users u ON m.sender_id = u.id WHERE m.id = ? "); $stmt->execute([$message_id]); $new_message = $stmt->fetch(); echo json_encode([ 'success' => true, 'message' => 'Message sent', 'data' => $new_message ]); } else { echo json_encode(['success' => false, 'message' => 'Failed to send message']); } ?> 7. API - Get Messages (api/get_messages.php)
<?php require_once '../includes/config.php'; require_once '../includes/auth.php'; header('Content-Type: application/json'); if (!isLoggedIn()) { echo json_encode(['success' => false, 'message' => 'Not authenticated']); exit; } $user_id = $_SESSION['user_id']; $chat_with = $_GET['chat_with'] ?? 0; $last_id = $_GET['last_id'] ?? 0; if (!$chat_with) { echo json_encode(['success' => false, 'message' => 'No chat partner specified']); exit; } // Get messages $query = " SELECT m.*, u.full_name as sender_name, u.avatar as sender_avatar FROM messages m JOIN users u ON m.sender_id = u.id WHERE (m.sender_id = ? AND m.receiver_id = ? AND m.deleted_by_sender = 0) OR (m.sender_id = ? AND m.receiver_id = ? AND m.deleted_by_receiver = 0) "; $params = [$user_id, $chat_with, $chat_with, $user_id]; if ($last_id > 0) { $query .= " AND m.id > ?"; $params[] = $last_id; } $query .= " ORDER BY m.created_at ASC"; $stmt = $pdo->prepare($query); $stmt->execute($params); $messages = $stmt->fetchAll(); // Get typing status $typing_stmt = $pdo->prepare(" SELECT is_typing FROM typing_status WHERE user_id = ? AND chatting_with = ? "); $typing_stmt->execute([$chat_with, $user_id]); $typing = $typing_stmt->fetch(); // Get partner status $status_stmt = $pdo->prepare("SELECT status, last_seen FROM users WHERE id = ?"); $status_stmt->execute([$chat_with]); $partner_status = $status_stmt->fetch(); echo json_encode([ 'success' => true, 'messages' => $messages, 'typing' => $typing ? (bool)$typing['is_typing'] : false, 'partner_status' => $partner_status ]); ?> 8. JavaScript - Chat Functionality (assets/js/chat.js)
// Chat Application JavaScript let currentUserId = null; let currentChatWith = null; let lastMessageId = 0; let typingTimer = null; let isTyping = false; document.addEventListener('DOMContentLoaded', function() { // Get current user ID from session (passed via PHP) currentUserId = document.querySelector('meta[name="user-id"]')?.content; currentChatWith = document.getElementById('receiver-id')?.value; if (currentChatWith) { startMessagePolling(); setupTypingIndicator(); setupEmojiPicker(); markMessagesAsRead(); } setupUserSearch(); setupEnterKey(); }); // Poll for new messages every 2 seconds function startMessagePolling() { pollMessages(); setInterval(pollMessages, 2000); } function pollMessages() { if (!currentChatWith) return; fetch(`api/get_messages.php?chat_with=${currentChatWith}&last_id=${lastMessageId}`) .then(response => response.json()) .then(data => { if (data.success) { // Update messages if (data.messages.length > 0) { appendMessages(data.messages); lastMessageId = data.messages[data.messages.length - 1].id; } // Update typing indicator updateTypingIndicator(data.typing); // Update partner status updatePartnerStatus(data.partner_status); } }) .catch(error => console.error('Error polling messages:', error)); } function appendMessages(messages) { const container = document.getElementById('messages-container'); messages.forEach(msg => { const isMe = msg.sender_id == currentUserId; const messageHtml = createMessageHtml(msg, isMe); // Remove loading indicator if present const loading = container.querySelector('.loading'); if (loading) loading.remove(); container.insertAdjacentHTML('beforeend', messageHtml); }); // Scroll to bottom container.scrollTop = container.scrollHeight; } function createMessageHtml(message, isMe) { const time = new Date(message.created_at).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); let content = ''; if (message.message) { content += `<p class="message-text">${escapeHtml(message.message)}</p>`; } if (message.file_path) { const fileUrl = message.file_path; const fileType = message.file_type; if (fileType.startsWith('image/')) { content += `<img src="${fileUrl}" class="message-image" onclick="viewImage('${fileUrl}')">`; } else { content += `<a href="${fileUrl}" target="_blank" class="message-file">📎 Download File</a>`; } } const readReceipt = isMe ? (message.is_read ? '✓✓' : '✓') : ''; return ` <div class="message ${isMe ? 'message-out' : 'message-in'}" data-id="${message.id}"> <div class="message-content"> ${content} <div class="message-meta"> <span class="message-time">${time}</span> <span class="message-status">${readReceipt}</span> </div> </div> </div> `; } function sendMessage() { const input = document.getElementById('message-text'); const message = input.value.trim(); const receiverId = document.getElementById('receiver-id').value; if (!message && !fileToUpload) return; const formData = new FormData(); formData.append('receiver_id', receiverId); formData.append('message', message); // Add file if exists const fileInput = document.getElementById('file-input'); if (fileInput.files.length > 0) { formData.append('file', fileInput.files[0]); } fetch('api/send_message.php', { method: 'POST', body: formData }) .then(response => response.json()) .then(data => { if (data.success) { input.value = ''; fileInput.value = ''; // Immediately append the message appendMessages([data.data]); } else { alert('Failed to send message: ' + data.message); } }) .catch(error => console.error('Error sending message:', error)); } // Typing indicator function setupTypingIndicator() { const input = document.getElementById('message-text'); input.addEventListener('input', function() { if (!isTyping) { isTyping = true; updateTypingStatus(true); } clearTimeout(typingTimer); typingTimer = setTimeout(function() { isTyping = false; updateTypingStatus(false); }, 1000); }); } function updateTypingStatus(typing) { const receiverId = document.getElementById('receiver-id').value; fetch('api/typing.php', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: `receiver_id=${receiverId}&typing=${typing ? 1 : 0}` }); } function updateTypingIndicator(isTyping) { const indicator = document.getElementById('typing-indicator'); if (indicator) { indicator.style.display = isTyping ? 'flex' : 'none'; } } // Mark messages as read function markMessagesAsRead() { const receiverId = document.getElementById('receiver-id').value; fetch('api/mark_read.php', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: `sender_id=${receiverId}` }); } // Emoji picker function setupEmojiPicker() { const picker = new EmojiMart.Picker({ onSelect: emoji => { const input = document.getElementById('message-text'); input.value += emoji.native; input.focus(); } }); document.getElementById('emoji-picker').appendChild(picker); document.getElementById('emoji-btn').addEventListener('click', function() { const picker = document.getElementById('emoji-picker'); picker.style.display = picker.style.display === 'none' ? 'block' : 'none'; }); } // File upload function uploadFile() { sendMessage(); // Reuse send message function } // User search function setupUserSearch() { const searchInput = document.getElementById('search-users'); searchInput.addEventListener('input', function() { const term = this.value.toLowerCase(); const users = document.querySelectorAll('.user-item'); users.forEach(user => { const name = user.querySelector('h4').textContent.toLowerCase(); if (name.includes(term)) { user.style.display = 'flex'; } else { user.style.display = 'none'; } }); }); } // Enter key to send function setupEnterKey() { const input = document.getElementById('message-text'); input.addEventListener('keypress', function(e) { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); } }); } // Helper: Escape HTML function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } // Helper: View image fullscreen function viewImage(url) { const modal = document.createElement('div'); modal.className = 'image-modal'; modal.innerHTML = ` <span class="close">×</span> <img src="${url}" class="modal-content"> `; modal.onclick = function() { modal.remove(); }; document.body.appendChild(modal); } 9. Admin Dashboard (admin/dashboard.php)
<?php require_once '../includes/config.php'; require_once '../includes/auth.php'; Auth::checkAdmin(); // Get statistics $totalUsers = $pdo->query("SELECT COUNT(*) FROM users")->fetchColumn(); $totalMessages = $pdo->query("SELECT COUNT(*) FROM messages")->fetchColumn(); $onlineUsers = $pdo->query("SELECT COUNT(*) FROM users WHERE status = 'online'")->fetchColumn(); $newToday = $pdo->query("SELECT COUNT(*) FROM users WHERE DATE(created_at) = CURDATE()")->fetchColumn(); // Get recent users $recentUsers = $pdo->query(" SELECT * FROM users ORDER BY created_at DESC LIMIT 10 ")->fetchAll(); // Get recent messages $recentMessages = $pdo->query(" SELECT m.*, u1.full_name as sender_name, u2.full_name as receiver_name FROM messages m JOIN users u1 ON m.sender_id = u1.id JOIN users u2 ON m.receiver_id = u2.id ORDER BY m.created_at DESC LIMIT 10 ")->fetchAll(); ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Admin Dashboard - <?php echo SITE_NAME; ?></title> <link rel="stylesheet" href="../assets/css/admin.css"> </head> <body> <div class="admin-wrapper"> <!-- Sidebar --> <aside class="sidebar"> <div class="sidebar-header"> <h2>Admin Panel</h2> <p>Welcome, <?php echo $_SESSION['user_name']; ?></p> </div> <nav class="sidebar-nav"> <ul> <li><a href="dashboard.php" class="active">Dashboard</a></li> <li><a href="users.php">Manage Users</a></li> <li><a href="chats.php">Monitor Chats</a></li> <li><a href="broadcast.php">Broadcast</a></li> <li><a href="logout.php">Logout</a></li> </ul> </nav> </aside> <!-- Main Content --> <main class="admin-main"> <div class="container"> <h1>Dashboard</h1> <!-- Stats Cards --> <div class="stats-grid"> <div class="stat-card"> <div class="stat-icon">👥</div> <div class="stat-details"> <h3><?php echo $totalUsers; ?></h3> <p>Total Users</p> </div> </div> <div class="stat-card"> <div class="stat-icon">💬</div> <div class="stat-details"> <h3><?php echo $totalMessages; ?></h3> <p>Total Messages</p> </div> </div> <div class="stat-card"> <div class="stat-icon">🟢</div> <div class="stat-details"> <h3><?php echo $onlineUsers; ?></h3> <p>Online Now</p> </div> </div> <div class="stat-card"> <div class="stat-icon">📅</div> <div class="stat-details"> <h3><?php echo $newToday; ?></h3> <p>New Today</p> </div> </div> </div> <!-- Recent Users --> <div class="card"> <div class="card-header"> <h3>Recent Users</h3> <a href="users.php" class="btn btn-sm">View All</a> </div> <div class="card-body"> <table class="data-table"> <thead> <tr> <th>ID</th> <th>Username</th> <th>Full Name</th> <th>Email</th> <th>Status</th> <th>Joined</th> <th>Actions</th> </tr> </thead> <tbody> <?php foreach ($recentUsers as $user): ?> <tr> <td>#<?php echo $user['id']; ?></td> <td><?php echo $user['username']; ?></td> <td><?php echo $user['full_name']; ?></td> <td><?php echo $user['email']; ?></td> <td> <span class="badge badge-<?php echo $user['status']; ?>"> <?php echo $user['status']; ?> </span> </td> <td><?php echo date('M d, Y', strtotime($user['created_at'])); ?></td> <td> <a href="users.php?edit=<?php echo $user['id']; ?>" class="btn-small">Edit</a> <a href="users.php?block=<?php echo $user['id']; ?>" class="btn-small btn-danger">Block</a> </td> </tr> <?php endforeach; ?> </tbody> </table> </div> </div> <!-- Recent Messages --> <div class="card"> <div class="card-header"> <h3>Recent Messages</h3> <a href="chats.php" class="btn btn-sm">View All</a> </div> <div class="card-body"> <table class="data-table"> <thead> <tr> <th>From</th> <th>To</th> <th>Message</th> <th>Time</th> <th>Status</th> </tr> </thead> <tbody> <?php foreach ($recentMessages as $msg): ?> <tr> <td><?php echo $msg['sender_name']; ?></td> <td><?php echo $msg['receiver_name']; ?></td> <td><?php echo substr($msg['message'], 0, 50) . '...'; ?></td> <td><?php echo timeAgo($msg['created_at']); ?></td> <td> <?php if ($msg['is_read']): ?> <span class="badge badge-success">Read</span> <?php else: ?> <span class="badge badge-warning">Unread</span> <?php endif; ?> </td> </tr> <?php endforeach; ?> </tbody> </table> </div> </div> </div> </main> </div> </body> </html> 10. CSS Styling (assets/css/chat.css)
/* Chat Application Specific Styles */ * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif; height: 100vh; overflow: hidden; } .chat-app { display: flex; height: 100vh; background: #f0f2f5; } /* Sidebar Styles */ .sidebar { width: 350px; background: white; border-right: 1px solid #e0e0e0; display: flex; flex-direction: column; } .sidebar-header { padding: 20px; background: #f5f5f5; border-bottom: 1px solid #e0e0e0; display: flex; justify-content: space-between; align-items: center; } .user-profile { display: flex; align-items: center; gap: 12px; } .user-profile .avatar { width: 48px; height: 48px; border-radius: 50%; object-fit: cover; } .user-info h3 { font-size: 16px; margin-bottom: 4px; } .user-info .status { font-size: 12px; color: #4caf50; } .sidebar-actions { display: flex; gap: 8px; } .btn-icon { width: 36px; height: 36px; border: none; background: transparent; border-radius: 50%; cursor: pointer; font-size: 20px; display: flex; align-items: center; justify-content: center; transition: background 0.2s; } .btn-icon:hover { background: #e0e0e0; } .search-box { padding: 15px; border-bottom: 1px solid #e0e0e0; } .search-box input { width: 100%; padding: 10px 15px; border: 1px solid #e0e0e0; border-radius: 20px; font-size: 14px; outline: none; } .search-box input:focus { border-color: #0084ff; } .users-list { flex: 1; overflow-y: auto; } .user-item { display: flex; align-items: center; padding: 15px; text-decoration: none; color: inherit; border-bottom: 1px solid #f0f0f0; transition: background 0.2s; position: relative; } .user-item:hover { background: #f5f5f5; } .user-item.active { background: #e3f2fd; } .user-avatar { position: relative; margin-right: 12px; } .user-avatar img { width: 48px; height: 48px; border-radius: 50%; object-fit: cover; } .status-dot { position: absolute; bottom: 2px; right: 2px; width: 12px; height: 12px; border-radius: 50%; border: 2px solid white; } .status-dot.online { background: #4caf50; } .status-dot.offline { background: #9e9e9e; } .user-details { flex: 1; } .user-details h4 { font-size: 16px; margin-bottom: 4px; } .last-message { font-size: 13px; color: #666; } .unread-badge { background: #0084ff; color: white; font-size: 12px; font-weight: bold; min-width: 20px; height: 20px; border-radius: 10px; display: flex; align-items: center; justify-content: center; padding: 0 6px; } /* Chat Area */ .chat-area { flex: 1; display: flex; flex-direction: column; background: #f0f2f5; } .chat-header { padding: 15px 20px; background: white; border-bottom: 1px solid #e0e0e0; display: flex; justify-content: space-between; align-items: center; } .chat-user-info { display: flex; align-items: center; gap: 12px; } .chat-user-info .avatar { width: 40px; height: 40px; border-radius: 50%; object-fit: cover; } .chat-user-info h3 { font-size: 16px; margin-bottom: 4px; } .user-status { font-size: 12px; color: #666; } /* Messages Container */ .messages-container { flex: 1; overflow-y: auto; padding: 20px; display: flex; flex-direction: column; gap: 10px; } .message { display: flex; margin-bottom: 10px; } .message-in { justify-content: flex-start; } .message-out { justify-content: flex-end; } .message-content { max-width: 60%; padding: 10px 15px; border-radius: 18px; position: relative; word-wrap: break-word; } .message-in .message-content { background: white; border-bottom-left-radius: 4px; } .message-out .message-content { background: #0084ff; color: white; border-bottom-right-radius: 4px; } .message-text { margin-bottom: 5px; line-height: 1.4; } .message-meta { font-size: 11px; opacity: 0.7; display: flex; justify-content: flex-end; align-items: center; gap: 5px; } .message-image { max-width: 200px; max-height: 200px; border-radius: 8px; cursor: pointer; margin-bottom: 5px; } .message-file { display: inline-block; padding: 5px 10px; background: rgba(0,0,0,0.1); border-radius: 4px; text-decoration: none; color: inherit; margin-bottom: 5px; } /* Typing Indicator */ .typing-indicator { padding: 10px 20px; background: white; border-top: 1px solid #e0e0e0; display: flex; align-items: center; gap: 5px; font-size: 14px; color: #666; } .dots span { animation: dots 1.5s infinite; opacity: 0; } .dots span:nth-child(1) { animation-delay: 0s; } .dots span:nth-child(2) { animation-delay: 0.2s; } .dots span:nth-child(3) { animation-delay: 0.4s; } @keyframes dots { 0%, 100% { opacity: 0; } 50% { opacity: 1; } } /* Message Input */ .message-input { padding: 15px 20px; background: white; border-top: 1px solid #e0e0e0; display: flex; align-items: center; gap: 10px; } .message-input input[type="text"] { flex: 1; padding: 12px 15px; border: 1px solid #e0e0e0; border-radius: 24px; font-size: 14px; outline: none; } .message-input input[type="text"]:focus { border-color: #0084ff; } .btn-send { padding: 10px 24px; background: #0084ff; color: white; border: none; border-radius: 24px; font-size: 14px; font-weight: 600; cursor: pointer; transition: background 0.2s; } .btn-send:hover { background: #0073e6; } /* Emoji Picker */ #emoji-picker { position: absolute; bottom: 80px; right: 20px; z-index: 1000; background: white; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.2); } /* No Chat Selected */ .no-chat-selected { flex: 1; display: flex; align-items: center; justify-content: center; background: #f0f2f5; } .welcome-message { text-align: center; color: #666; } .welcome-message h2 { font-size: 24px; margin-bottom: 10px; color: #333; } /* Image Modal */ .image-modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.9); display: flex; align-items: center; justify-content: center; z-index: 2000; } .image-modal .modal-content { max-width: 90%; max-height: 90%; object-fit: contain; } .image-modal .close { position: absolute; top: 20px; right: 30px; color: white; font-size: 40px; font-weight: bold; cursor: pointer; } /* Loading */ .loading { text-align: center; color: #666; padding: 20px; } /* Scrollbar */ ::-webkit-scrollbar { width: 6px; } ::-webkit-scrollbar-track { background: #f1f1f1; } ::-webkit-scrollbar-thumb { background: #888; border-radius: 3px; } ::-webkit-scrollbar-thumb:hover { background: #555; } 📝 How to Use This Project (Step-by-Step Guide)
Step 1: Install Local Server
- Download and install XAMPP from apachefriends.org
- Launch XAMPP Control Panel
- Start Apache and MySQL services
Step 2: Create Project Folder
- Navigate to
C:\xampp\htdocs\(Windows) or/Applications/XAMPP/htdocs/(Mac) - Create a new folder named
chat-application
Step 3: Set Up Database
- Open browser and go to
http://localhost/phpmyadmin - Click on "SQL" tab
- Copy the entire SQL from
database/chat_app.sql - Paste and click "Go" to create database and tables
Step 4: Create Password Hashes
Create a file named hash.php in project root:
<?php echo "password123 hash: " . password_hash('password123', PASSWORD_DEFAULT); ?> Run it: http://localhost/chat-application/hash.php
Copy the hash and update it in the SQL insert statements for the users.
Step 5: Configure Database Connection
Open includes/config.php and verify:
define('DB_HOST', 'localhost'); define('DB_NAME', 'chat_app'); define('DB_USER', 'root'); define('DB_PASS', ''); Step 6: Create Upload Directories
Create these folders and set permissions:
assets/uploads/- for file sharingassets/images/avatars/- for profile pictures- Copy a default avatar image as
default.pngin avatars folder
Step 7: Test the Application
Access Points:
- Main Site:
http://localhost/chat-application/ - Login:
http://localhost/chat-application/login.php - Register:
http://localhost/chat-application/register.php - Admin:
http://localhost/chat-application/admin/
Test Accounts:
| Role | Username | Password |
|---|---|---|
| Admin | admin | password123 |
| User 1 | john | password123 |
| User 2 | jane | password123 |
Step 8: Test Features
User Features:
- Register a new account
- Login with credentials
- View online users in sidebar
- Select a user to chat with
- Send text messages
- Send emojis (click 😊 button)
- Share files/images (click 📎 button)
- See typing indicators
- View read receipts (✓ for delivered, ✓✓ for read)
Admin Features:
- Login as admin
- View dashboard statistics
- Manage users (edit/block/delete)
- Monitor all chats
- Send broadcast announcements
🎯 Features Summary
User Features:
- ✅ User registration and login
- ✅ Profile management
- ✅ Online/offline status
- ✅ Real-time messaging (polling)
- ✅ Emoji support
- ✅ File/image sharing
- ✅ Typing indicators
- ✅ Read receipts
- ✅ Message history
- ✅ Search users
Admin Features:
- ✅ Dashboard with statistics
- ✅ User management
- ✅ Chat monitoring
- ✅ Broadcast announcements
- ✅ View all messages
- ✅ Block/unblock users
Technical Features:
- ✅ Secure password hashing
- ✅ Session management
- ✅ AJAX polling for real-time
- ✅ File upload handling
- ✅ SQL injection prevention
- ✅ XSS protection
- ✅ Responsive design
- ✅ Foreign key constraints
🚀 Future Enhancements
- WebSockets: Replace polling with WebSockets for true real-time
- Group Chats: Create group conversations
- Voice/Video Calls: WebRTC integration
- Push Notifications: Browser notifications
- End-to-End Encryption: Message encryption
- Message Reactions: Like/emoji reactions
- Message Editing/Deletion: Edit or delete sent messages
- User Blocking: Block unwanted users
- Chat Search: Search through message history
- Dark Mode: Theme switching
- Mobile Apps: React Native or Flutter apps
- Message Backup: Export chat history
- Custom Emojis: Upload custom emojis
- Voice Messages: Record and send voice
- Location Sharing: Share real-time location
This comprehensive Chat Application provides a solid foundation for real-time communication that can be extended with additional features as needed!