PHP & MySQL
Star Rating System - PHP, MySQL & AJAX
Build an interactive star rating system with PHP, MySQL, and AJAX. Includes hover effects, half-star support, and anti-fraud measures.
Database Schema
CREATE TABLE ratings (
id INT AUTO_INCREMENT PRIMARY KEY,
item_id INT NOT NULL,
user_ip VARCHAR(45) NOT NULL,
score TINYINT NOT NULL CHECK (score BETWEEN 1 AND 5),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY unique_vote (item_id, user_ip),
INDEX idx_item (item_id)
);
PHP Backend - Submit & Fetch Ratings
<?php
// rate.php
header('Content-Type: application/json');
$pdo = new PDO('mysql:host=localhost;dbname=app', 'user', 'pass', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
]);
$itemId = (int) ($_POST['item_id'] ?? 0);
$score = (int) ($_POST['score'] ?? 0);
$ip = $_SERVER['REMOTE_ADDR'];
if ($itemId < 1 || $score < 1 || $score > 5) {
echo json_encode(['error' => 'Invalid input']);
exit;
}
// Upsert - one vote per IP per item
$stmt = $pdo->prepare("
INSERT INTO ratings (item_id, user_ip, score)
VALUES (?, ?, ?)
ON DUPLICATE KEY UPDATE score = VALUES(score)
");
$stmt->execute([$itemId, $ip, $score]);
// Return updated average
$stmt = $pdo->prepare("
SELECT ROUND(AVG(score), 1) AS avg_score,
COUNT(*) AS total_votes
FROM ratings WHERE item_id = ?
");
$stmt->execute([$itemId]);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
echo json_encode([
'avg' => (float) $result['avg_score'],
'total' => (int) $result['total_votes'],
'your_score' => $score,
]);
CSS Star Display
.stars {
display: inline-flex;
direction: rtl; /* right-to-left for hover trick */
gap: 2px;
}
.stars input { display: none; }
.stars label {
cursor: pointer;
font-size: 28px;
color: #ddd;
transition: color 0.15s;
}
.stars label::before { content: '\2605'; } /* ★ */
/* Highlight on hover and when checked */
.stars input:checked ~ label,
.stars label:hover,
.stars label:hover ~ label {
color: #f2bf59;
}
/* Average display (read-only, uses width percentage) */
.stars-avg {
position: relative;
display: inline-block;
font-size: 24px;
letter-spacing: 3px;
color: #ddd;
}
.stars-avg::before { content: '\2605\2605\2605\2605\2605'; }
.stars-avg .fill {
position: absolute; top: 0; left: 0;
overflow: hidden; white-space: nowrap;
color: #f2bf59;
}
.stars-avg .fill::before { content: '\2605\2605\2605\2605\2605'; }
JavaScript - Interactive Rating
document.querySelectorAll('.stars input').forEach(input => {
input.addEventListener('change', async function() {
const itemId = this.closest('.rating-widget').dataset.itemId;
const score = this.value;
const res = await fetch('rate.php', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: `item_id=${itemId}&score=${score}`
});
const data = await res.json();
// Update display
const widget = this.closest('.rating-widget');
widget.querySelector('.avg').textContent = data.avg;
widget.querySelector('.count').textContent =
`(${data.total} vote${data.total !== 1 ? 's' : ''})`;
});
});
Accessible HTML
<div class="rating-widget" data-item-id="42" role="radiogroup"
aria-label="Rate this item">
<div class="stars">
<input type="radio" name="rating-42" value="5" id="r42-5">
<label for="r42-5" aria-label="5 stars"></label>
<input type="radio" name="rating-42" value="4" id="r42-4">
<label for="r42-4" aria-label="4 stars"></label>
<input type="radio" name="rating-42" value="3" id="r42-3">
<label for="r42-3" aria-label="3 stars"></label>
<input type="radio" name="rating-42" value="2" id="r42-2">
<label for="r42-2" aria-label="2 stars"></label>
<input type="radio" name="rating-42" value="1" id="r42-1">
<label for="r42-1" aria-label="1 star"></label>
</div>
<span class="avg">4.2</span>
<span class="count">(128 votes)</span>
</div>
Last updated: 2026 • Browse all courses