Rabikant
Posted on March 9th
How to Build a Live Leaderboard App with PHP, HTML, and WebSockets
"let's Learn how to Build a Live Leaderboard App with PHP, HTML, and WebSockets"
In this blog, we will walk through the creation of a real-time live leaderboard using JavaScript, PHP, and PieSocket for WebSocket-based real-time updates. This project will allow players to update their scores, which will automatically reflect on the leaderboard without refreshing the page.
Features of the Live Leaderboard
- Displays a ranking table with players and their scores.
- Updates scores dynamically using a simple form.
- Uses WebSockets (PieSocket) for real-time score updates.
- Backend integration with PHP for handling score updates.
Basic HTML Structure & External Libraries
At the beginning of the file, we include essential metadata and external libraries.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Live Leaderboard</title>
<script src="<https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js>"></script>
<script src="<https://unpkg.com/piesocket-js@5>"></script>
</head>
- meta charset="UTF-8" → Ensures proper text encoding.
- meta name="viewport" → Makes the page responsive on different devices.
- Axios Library → Used for making HTTP requests.
- PieSocket Library → Enables real-time WebSocket communication.
CSS Styling for Leaderboard
The embedded CSS styles the page with a dark theme and a clean leaderboard layout.
<style>
body {
font-family: 'Arial', sans-serif;
background-color: #121212;
color: #fff;
text-align: center;
padding: 20px;
}
h2 {
font-size: 2rem;
}
table {
width: 60%;
margin: auto;
border-collapse: collapse;
background: #1e1e1e;
border-radius: 10px;
overflow: hidden;
}
th {
background: #333;
color: #ffcc00;
padding: 15px;
}
td {
background: #222;
padding: 15px;
}
</style>
- The background is dark (#121212).
- The table has a rounded border, alternating row colors, and bold headers.
- The gold color (#ffcc00) highlights important elements.
Leaderboard Table Structure
The table is structured with headers and a body where player scores will be dynamically injected.
<body>
<h2>Live Leaderboard</h2>
<table>
<thead>
<tr>
<th>Rank</th>
<th>Player</th>
<th>Score</th>
</tr>
</thead>
<tbody id="leaderboard"></tbody>
</table>
Score Update Form
A simple form allows users to update scores.
<h3>Update Score</h3>
<div id="scoreDiv">
<label for="player">Player Name:</label>
<select id="player">
<option value="Player 1">Player 1</option>
<option value="Player 2">Player 2</option>
<option value="Player 3">Player 3</option>
</select>
<label for="score">New Score:</label>
<input type="number" id="score" required>
<button type="button" id="updateButton">Update Scores</button>
</div>
- The user selects a player from a dropdown. Inputs a new score .
- Clicks a button to submit the update.
JavaScript for Fetching and Updating Scores
Setting Up Event Listeners and Variables
document.addEventListener("DOMContentLoaded", function () {
let leaderboardTable = document.getElementById("leaderboard");
let playerNameInput = document.getElementById("player");
let playerScoreInput = document.getElementById("score");
let updateButton = document.getElementById("updateButton");
let scores = {};
- Purpose: Ensures the script runs only after the HTML document is fully loaded.
- Variables:
- leaderboardTable: The section of the leaderboard where player scores will be displayed.
- playerNameInput: A dropdown where the user selects a player. playerScoreInput: An field where the user enters a new score.
- updateButton: A button that submits the updated score.
- scores: An object to store player scores locally.
Handling Score Updates
updateButton.addEventListener("click", function (e) {
e.preventDefault();
console.log("Button clicked");
let playerName = playerNameInput.value.trim();
let playerScore = parseInt(playerScoreInput.value);
if (playerName === "" || isNaN(playerScore)) {
alert("Enter a valid player name and score!");
return;
}
// Update the local scores object.
scores[playerName] = playerScore;
// Make an Axios POST request to update the backend.
axios.post("<http://127.0.0.1:8000/update_score.php>", {
player: { name: playerName, score: playerScore }
})
.then(response => {
console.log("Score updated successfully:", response.data);
updateLeaderboard(); // Refresh leaderboard after updating scores
})
.catch(error => {
console.error("Error updating score:", error);
});
playerScoreInput.value = "";
});
- Event Listener: When the update button is clicked:
- Prevents the default form submission behavior.
- Logs the button click event.
- Extracts player name and score.
- Validates inputs (prevents empty names or non-numeric scores).
- Updates the scores object locally.
- Sends the new score to the backend using Axios (HTTP POST request).
- If the request is successful, it calls updateLeaderboard() to refresh the leaderboard.
Setting Up Real-Time Updates with PieSocket
var piesocket = new PieSocket.default({
clusterId: "CLUSTER_ID",
apiKey: "API_KEY",
notifySelf: true,
presence: true
});
piesocket.subscribe("leaderboard-channel").then(ch => {
channel = ch;
channel.listen("scoreUpdate", function (data) {
console.log("Received event from PieSocket:", data);
if (data && data.name && data.score !== undefined) {
scores[data.name] = data.score;
updateLeaderboard();
}
});
});
- PieSocket Initialization: Connects to the WebSocket service to listen for real-time leaderboard updates.
- Subscribing to a Channel: Joins leaderboard-channel to receive live updates.
- Listening for Score Updates:
- When a scoreUpdate event is received, it updates the scores object.
- Calls updateLeaderboard() to reflect changes in the UI.
Fetching Leaderboard Data
function updateLeaderboard() {
// Fetch the leaderboard data from get_score.php
axios.get("<http://127.0.0.1:8000/get_score.php>")
.then(response => {
if (response.data.status === "success") {
const scores = response.data.data;
// Update the table with retrieved data
leaderboardTable.innerHTML = "";
scores.forEach((player, index) => {
let row = document.createElement("tr");
row.innerHTML = `<td>${index + 1}</td><td>${player.player_name}</td><td>${player.score}</td>`;
leaderboardTable.appendChild(row);
});
console.log("Leaderboard updated with data from the database!");
} else {
console.error("Error fetching leaderboard data:", response.data);
}
})
.catch(error => {
console.error("Error fetching leaderboard data:", error);
});
}
- Purpose: Fetches the leaderboard data from get_score.php using an Axios GET request.
- If successful:
- Extracts player scores from the response.
- Clears the leaderboard table.
- Dynamically populates rows with player names and scores.
- Logs the update.
- If failed:
- Logs an error message.
Initial Leaderboard Fetch
updateLeaderboard();
});
- Calls updateLeaderboard() once when the page loads to display the current scores.
Setting HTTP Headers for CORS
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Methods: POST, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type");
header("Content-Type: application/json");
- These headers allow cross-origin requests from any domain ().
- It enables only POST and OPTIONS methods.
- Content-Type: application/json ensures that requests and responses use JSON.
Handling Preflight Requests
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(200);
exit();
}
- Browsers send OPTIONS requests before POST requests (CORS preflight).
- If it's an OPTIONS request, the script exits immediately with 200 OK.
Reading and Validating Incoming JSON Data
$data = json_decode(file_get_contents('php://input'), true);
if (!isset($data['player']['name']) || !isset($data['player']['score'])) {
http_response_code(400);
echo json_encode(["error" => "Invalid request"]);
exit();
}
- Reads raw JSON input and converts it into a PHP array ($data).
- Validates that the required fields (name and score) exist.
- If missing, returns 400 Bad Request and exits.
Extracting and Sanitizing Input
$playerName = $data['player']['name'];
$playerScore = intval($data['player']['score']);
- Extracts the player's name and score from the request.
- intval() ensures the score is always an integer (prevents injection attacks).
Connecting to the Database
$conn = new mysqli("localhost", "user", "pass", "leaderboard");
if ($conn->connect_error) {
http_response_code(500);
echo json_encode(["error" => "Database connection failed"]);
exit();
}
- Creates a connection to the MySQL database.
- If the connection fails, returns 500 Internal Server Error.
Inserting or Updating Player Score
$sql = "INSERT INTO scores (player_name, score) VALUES (?, ?)
ON DUPLICATE KEY UPDATE score = VALUES(score)";
$stmt = $conn->prepare($sql);
$stmt->bind_param("si", $playerName, $playerScore);
$stmt->execute();
$stmt->close();
$conn->close();
- Prepares a SQL query to insert/update the player's score.
- Uses ON DUPLICATE KEY UPDATE:
- If the player exists → Updates their score.
- If not → Inserts a new entry.
- Prevents SQL injection by using bind_param("si", $playerName, $playerScore).
Setting Up PieSocket WebSocket Notification
$apiKey = "API_KEY";
$apiSecret = "API_SECRET";
$roomId = "leaderboard-channel";
$postData = [
"key" => $apiKey,
"secret" => $apiSecret,
"roomId" => $roomId,
"message" => [
"event" => "scoreUpdate",
"data" => [
"name" => $playerName,
"score" => $playerScore
]
]
];
- Defines PieSocket API credentials ($apiKey, $apiSecret).
- Creates a message payload to notify the leaderboard-channel about the updated score.
Sending WebSocket Notification via cURL
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => "<https://free.blr2.piesocket.com/api/publish>",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode($postData),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
- Initializes cURL to send an HTTP POST request to PieSocket.
- Sends the JSON-encoded postData.
- Stores the response in $response and the HTTP status code in $httpCode.
Responding with JSON Output
echo json_encode([
"status" => "success",
"http_code" => $httpCode,
"piesocket_response" => $response
]);
- Sends a JSON response back to the frontend.
- Includes:
- "status": "success"
- "http_code" from PieSocket response.
- "piesocket_response" (WebSocket server response).
Setting HTTP Headers for CORS
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Methods: GET, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type");
header("Content-Type: application/json");
- Access-Control-Allow-Origin: * → Allows requests from any domain.
- Access-Control-Allow-Methods: GET, OPTIONS → Limits requests to GET (for fetching data) and OPTIONS (for CORS preflight).
- Access-Control-Allow-Headers: Content-Type → Allows the client to send Content-Type in requests.
- Content-Type: application/json → Ensures the response is JSON.
Connecting to the MySQL Database
$conn = new mysqli("localhost", "user", "pass", "leaderboard");
if ($conn->connect_error) {
http_response_code(500);
echo json_encode(["error" => "Database connection failed"]);
exit();
}
- Creates a MySQL database connection using mysqli.
- If the connection fails, it:
- Returns HTTP status 500 (Internal Server Error)
- Sends a JSON error message: { "error": "Database connection failed" }
- Exits the script to prevent further execution.
Querying the Leaderboard Data
$sql = "SELECT player_name, score FROM scores ORDER BY score DESC";
$result = $conn->query($sql);
- The SQL query retrieves all player names and scores from the scores table.
- Orders them by score DESC, meaning:
- Highest scores appear first.
- Players with the same score appear in their natural order.
Fetching & Formatting the Data
$scores = [];
while ($row = $result->fetch_assoc()) {
$scores[] = $row;
}
- Creates an empty array ($scores) to store results.
- Loops through the query result:
- Each row is fetched using $result->fetch_assoc().
- The row is added to the $scores array.
Closing the Database Connection
$conn->close();
- Closes the database connection to free up resources.
Returning JSON Response
echo json_encode(["status" => "success", "data" => $scores]);
- Sends a JSON response containing:
- "status": "success" → Indicates success.
- "data": [...] → Contains an array of players and their scores.
Running The App:
Start a PHP server:
php -S 127.0.0.1:8000
And open the html file in a browser.
Complete Code
The project is available on our GitHub : https://github.com/piehostHQ/LeaderBoard
