In this tutorial, we'll be creating a commenting system with PHP, MySQL, and AJAX (JavaScript). The commenting system will add functionally to your website (blog, news website, etc.) that your guests can use to submit content and share opinions.

The commenting system we'll be developing today will be minimal, clean, and fast! And with the use of AJAX, you can seamlessly implement the system on any webpage.

Submitted comments will be stored and retrieved in and from a MySQL database and subsequently populated with PHP and HTML.

The Advanced package includes additional features and a download link to the source code.

1. Getting Started

You will need to install a web server environment if you're going to test the commenting system on your own computer system. Follow the below instructions.

  • Install a web server environment solution package — I recommend you install XAMPP.
  • If you have your own production server (VPS, Dedicated, etc.), you'll need to install PHP, Apache, MySQL, and phpMyAdmin (Note: these are already included with XAMPP).
  • Install a code editor. You can use Notepad, but I don't recommend it, use one of the following instead: Notepad++, Visual Studio Code, or Atom.

2. Creating the Database and setting-up Tables

We need to create the MySQL database and create the comments table, as that is what we'll use to store and retrieve all our comments. We can do that with phpMyAdmin.

Navigate to phpMyAdmin (e.g. http://localhost/phpmyadmin/) in your browser and follow the below instructions:

  • Click the Databases tab at the top
  • Under Create database, type in phpcomments in the text box
  • Select utf8_general_ci as the collation (UTF-8 is the default encoding in HTML5)
  • Click Create

While the database is selected, click the SQL tab and execute the following statement code:

SQL
CREATE TABLE IF NOT EXISTS `comments` (
	`id` int(11) NOT NULL AUTO_INCREMENT,
	`page_id` int(11) NOT NULL,
	`parent_id` int(11) NOT NULL DEFAULT '-1',
	`name` varchar(255) NOT NULL,
	`content` text NOT NULL,
	`submit_date` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
	PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

INSERT INTO `comments` (`id`, `page_id`, `parent_id`, `name`, `content`, `submit_date`) VALUES
(1, 1, -1, 'John Doe', 'Thank you for taking the time to write this article, I really enjoyed reading it!\r\n\r\nThank you, David!', '2020-07-22 14:35:15'),
(2, 1, 11, 'David Adams', 'It''s good to hear that you enjoyed this article.', '2020-07-22 14:36:19'),
(3, 1, -1, 'Michael', 'I appreciate the time and effort you spent writing this article, good job!', '2020-07-22 14:37:43');

The above SQL statement will create the comments table with the following columns:

  • id — The unique comment ID.
  • page_id — This will determine which comment is for which page. It will be the page ID that you can specify on any webpage.
  • parent_id — The parent comment ID that we'll use for comment replies.
  • name — The name of the user (e.g., Joe Bloggs).
  • content — The comment content will be what the user inputs via the form.
  • submit_date — The date the comment was posted.

The data that we insert into the comments table will be for testing purposes.

In phpMyAdmin, the table structure should resemble the following:

http://localhost/phpmyadmin/
MySQL Comments Table Structure

3. Creating the Stylesheets (CSS3)

We'll be creating two stylesheets for our commenting system - one will be for our home page, as this page will be used as an example of how we'll implement the commenting system. The other stylesheet will be used for the commenting system itself.

Stylesheets are used to structure the layout of our commenting system and make them look more appealing.

Create the style.css file and add the following:

CSS
* {
  box-sizing: border-box;
  font-family: -apple-system, BlinkMacSystemFont, "segoe ui", roboto, oxygen, ubuntu, cantarell, "fira sans", "droid sans", "helvetica neue", Arial, sans-serif;
  font-size: 16px;
}
body {
  background-color: #FFFFFF;
  margin: 0;
}
.navtop {
  background-color: #3f69a8;
  height: 60px;
  width: 100%;
  border: 0;
}
.navtop div {
  display: flex;
  margin: 0 auto;
  padding: 0 25px;
  max-width: 1000px;
  width: 100%;
  height: 100%;
}
.navtop div h1, .navtop div a {
  display: inline-flex;
  align-items: center;
}
.navtop div h1 {
  flex: 1;
  font-size: 24px;
  padding: 0;
  margin: 0;
  color: #ecf0f6;
  font-weight: normal;
}
.navtop div a {
  padding: 0 20px;
  text-decoration: none;
  color: #c5d2e5;
  font-weight: bold;
}
.navtop div a i {
  padding: 2px 8px 0 0;
}
.navtop div a:hover {
  color: #ecf0f6;
}
.content {
  max-width: 1000px;
  width: 100%;
  margin: 0 auto;
  padding: 0 40px 50px 40px;
}
.content h2 {
  margin: 0;
  padding: 25px 0;
  font-size: 22px;
  border-bottom: 1px solid #ebebeb;
  color: #666666;
}

Create the comments.css file and add:

CSS
.comments .comment_header {
    display: flex;
    justify-content: space-between;
    border-bottom: 1px solid #eee;
    padding: 15px 0;
    margin-bottom: 10px;
    align-items: center;
}
.comments .comment_header .total {
    color: #777777;
    font-size: 14px;
}
.comments .comment_header .write_comment_btn {
    margin: 0;
}
.comments .write_comment_btn, .comments .write_comment button {
    display: inline-block;
    background-color: #565656;
    color: #fff;
    text-decoration: none;
    margin: 10px 0 0 0;
    padding: 5px 10px;
    border-radius: 5px;
    font-size: 14px;
    font-weight: 600;
    border: 0;
}
.comments .write_comment_btn:hover, .comments .write_comment button:hover {
    background-color: #636363;
}
.comments .write_comment {
    display: none;
    padding: 20px 0 10px 0;
}
.comments .write_comment textarea {
    width: 100%;
    padding: 10px;
    border: 1px solid #ddd;
    border-radius: 5px;
    height: 150px;
    margin-top: 10px;
}
.comments .write_comment input {
    display: block;
    width: 250px;
    padding: 10px;
    border: 1px solid #ddd;
    border-radius: 5px;
    margin-top: 10px;
}
.comments .write_comment button {
    cursor: pointer;
}
.comments .comment {
    padding-top: 10px;
}
.comments .comment .name {
    display: inline;
    padding: 0 5px 3px 0;
    margin: 0;
    font-size: 16px;
    color: #555555;
}
.comments .comment .date {
    color: #888888;
    font-size: 14px;
}
.comments .comment .content {
    padding: 5px 0 5px 0;
}
.comments .comment .reply_comment_btn {
    display: inline-block;
    text-decoration: none;
    margin-bottom: 10px;
    font-size: 14px;
    color: #888888;
}
.comments .comment .replies {
    padding-left: 30px;
}

Feel free to customize the stylesheets above and add your own CSS rules.

4. Creating the Commenting System with PHP

We will now start coding our commenting system with PHP. In this section, we're going to connect to the database using PDO, create the template functions, and execute queries using prepared statements (prevents SQL injection).

Create the comments.php file and add:

PHP
<?php
// Update the details below with your MySQL details
$DATABASE_HOST = 'localhost';
$DATABASE_USER = 'root';
$DATABASE_PASS = '';
$DATABASE_NAME = 'phpcomments';
try {
    $pdo = new PDO('mysql:host=' . $DATABASE_HOST . ';dbname=' . $DATABASE_NAME . ';charset=utf8', $DATABASE_USER, $DATABASE_PASS);
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $exception) {
    // If there is an error with the connection, stop the script and display the error
    exit('Failed to connect to database: ' . $exception->getMessage());
}

The code above will connect to our MySQL database using the PDO interface. Remember to update the connection variables to reflect your own details. If there is an error, it will output to the screen.

Add after:

PHP
// Below function will convert datetime to time elapsed string
function time_elapsed_string($datetime, $full = false) {
    $now = new DateTime;
    $ago = new DateTime($datetime);
    $diff = $now->diff($ago);
    $w = floor($diff->d / 7);
    $diff->d -= $w * 7;
    $string = ['y' => 'year','m' => 'month','w' => 'week','d' => 'day','h' => 'hour','i' => 'minute','s' => 'second'];
    foreach ($string as $k => &$v) {
        if ($k == 'w' && $w) {
            $v = $w . ' week' . ($w > 1 ? 's' : '');
        } else if (isset($diff->$k) && $diff->$k) {
            $v = $diff->$k . ' ' . $v . ($diff->$k > 1 ? 's' : '');
        } else {
            unset($string[$k]);
        }
    }
    if (!$full) $string = array_slice($string, 0, 1);
    return $string ? implode(', ', $string) . ' ago' : 'just now';
}

The above function will convert our date and time to an elapsed string, which will appear as "1 day ago" etc, in the commenting section.

Add after:

PHP
// This function will populate the comments and comments replies using a loop
function show_comments($comments, $parent_id = -1) {
    $html = '';
    if ($parent_id != -1) {
        // If the comments are replies sort them by the "submit_date" column
        array_multisort(array_column($comments, 'submit_date'), SORT_ASC, $comments);
    }
    // Iterate the comments using the foreach loop
    foreach ($comments as $comment) {
        if ($comment['parent_id'] == $parent_id) {
            // Add the comment to the $html variable
            $html .= '
            <div class="comment">
                <div>
                    <h3 class="name">' . htmlspecialchars($comment['name'], ENT_QUOTES) . '</h3>
                    <span class="date">' . time_elapsed_string($comment['submit_date']) . '</span>
                </div>
                <p class="content">' . nl2br(htmlspecialchars($comment['content'], ENT_QUOTES)) . '</p>
                <a class="reply_comment_btn" href="#" data-comment-id="' . $comment['id'] . '">Reply</a>
                ' . show_write_comment_form($comment['id']) . '
                <div class="replies">
                ' . show_comments($comments, $comment['id']) . '
                </div>
            </div>
            ';
        }
    }
    return $html;
}

This function will populate the array of comments and return the value in HTML format. The replies are populated by executing the same function along with the parent comment ID.

The htmlspecialchars function will convert special characters to HTML entities, which will prevent XSS attacks. To decode specific characters or HTML tags, we can leverage the str_replace function to replace specific special characters.

Add after:

PHP
// This function is the template for the write comment form
function show_write_comment_form($parent_id = -1) {
    $html = '
    <div class="write_comment" data-comment-id="' . $parent_id . '">
        <form>
            <input name="parent_id" type="hidden" value="' . $parent_id . '">
            <input name="name" type="text" placeholder="Your Name" required>
            <textarea name="content" placeholder="Write your comment here..." required></textarea>
            <button type="submit">Submit Comment</button>
        </form>
    </div>
    ';
    return $html;
}

This function contains the template for our "write comment" form, which visitors will use to write comments and submit them.

Add after:

PHP
// Page ID needs to exist, this is used to determine which comments are for which page
if (isset($_GET['page_id'])) {
    // Check if the submitted form variables exist
    if (isset($_POST['name'], $_POST['content'])) {
        // POST variables exist, insert a new comment into the MySQL comments table (user submitted form)
        $stmt = $pdo->prepare('INSERT INTO comments (page_id, parent_id, name, content, submit_date) VALUES (?,?,?,?,NOW())');
        $stmt->execute([ $_GET['page_id'], $_POST['parent_id'], $_POST['name'], $_POST['content'] ]);
        exit('Your comment has been submitted!');
    }
    // Get all comments by the Page ID ordered by the submit date
    $stmt = $pdo->prepare('SELECT * FROM comments WHERE page_id = ? ORDER BY submit_date DESC');
    $stmt->execute([ $_GET['page_id'] ]);
    $comments = $stmt->fetchAll(PDO::FETCH_ASSOC);
    // Get the total number of comments
    $stmt = $pdo->prepare('SELECT COUNT(*) AS total_comments FROM comments WHERE page_id = ?');
    $stmt->execute([ $_GET['page_id'] ]);
    $comments_info = $stmt->fetch(PDO::FETCH_ASSOC);
} else {
    exit('No page ID specified!');
}
?>

The code above will check if the page ID variable is specified, as this is used to determine which comments to show on which page. If the page ID is not specified, stop the script and output the error.

Comments are then retrieved from the database (ordered by the submit date in descending order) and are stored in an associative array. The total number of comments is calculated using the MySQL COUNT(*) function.

Add after:

PHP
<div class="comment_header">
    <span class="total"><?=$comments_info['total_comments']?> comments</span>
    <a href="#" class="write_comment_btn" data-comment-id="-1">Write Comment</a>
</div>

<?=show_write_comment_form()?>

<?=show_comments($comments)?>

This is the template for our commenting system. The comments are populated by executing the show_comments() function along with the comments associative array variable that we previously defined.

The show_write_comment_form() function will output the form that visitors can utilize to write and submit comments.

That's everything we need to code in this file. The next step is to implement the commenting system on a webpage using AJAX.

5. Implementing the Commenting System into our Webpage with AJAX

Now that we have our server-side PHP file created, we can implement the comment system on our webpage.

Create the index.html file and add:

HTML
<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>Comments System</title>
		<link href="style.css" rel="stylesheet" type="text/css">
		<link href="comments.css" rel="stylesheet" type="text/css">
	</head>
	<body>

	    <nav class="navtop">
	    	<div>
	    		<h1>Comments System</h1>
	    	</div>
	    </nav>

		<div class="content home">

			<h2>Article</h2>

			<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam tristique vel leo a vestibulum. Praesent varius ex elit, vitae pretium felis laoreet in. Nullam sit amet pretium nulla. Aliquam convallis sem vitae tincidunt pulvinar. Integer id ex efficitur, vulputate ante et, vulputate risus. Maecenas ac nunc est. Donec nisl turpis, aliquet quis turpis mollis, dictum pulvinar est. Vivamus ut suscipit turpis. Sed metus leo, dignissim non vehicula non, tincidunt ac velit. Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>

			<p>Nunc id lacinia mauris. Aliquam pellentesque orci et neque egestas, vel lobortis risus egestas. Curabitur non rhoncus nibh. Donec ante lorem, luctus eget ex eget, malesuada ornare nisl. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Etiam consectetur egestas magna non dignissim. Pellentesque sit amet mollis mauris. Nunc quis arcu ac diam tempus auctor. Proin nec commodo nisl. Duis gravida lorem quis ipsum mattis, id finibus tortor pretium. Nam maximus pretium nisi, ullamcorper tincidunt dolor sagittis ac. Donec suscipit neque lectus, id fringilla tortor pellentesque ut. Maecenas quam lectus, pharetra vitae commodo sit amet, iaculis quis massa. Aenean varius quam quis posuere viverra. Nullam varius condimentum sem, sit amet bibendum augue porttitor in.</p>

			<p>Morbi purus turpis, finibus vel fermentum nec, egestas eu elit. Fusce eleifend ac massa sit amet eleifend. Suspendisse sit amet facilisis augue. Praesent vitae dui lacus. Suspendisse sodales nisl massa, quis vehicula ante rutrum vitae. Proin scelerisque vestibulum purus, ac ultrices sapien malesuada vel. Proin sit amet tristique orci. Vestibulum in tortor ante.</p>
			
		</div>

	</body>
</html>

This is the sample page we'll use to display our comments. The file does not need to be in PHP format because we'll use AJAX to retrieve the comments from our comments.php page.

Add this code just before the closing body tag:

HTML
<div class="comments"></div>

<script>
const comments_page_id = 1; // This number should be unique on every page
fetch("comments.php?page_id=" + comments_page_id).then(response => response.text()).then(data => {
	document.querySelector(".comments").innerHTML = data;
	document.querySelectorAll(".comments .write_comment_btn, .comments .reply_comment_btn").forEach(element => {
		element.onclick = event => {
			event.preventDefault();
			document.querySelectorAll(".comments .write_comment").forEach(element => element.style.display = 'none');
			document.querySelector("div[data-comment-id='" + element.getAttribute("data-comment-id") + "']").style.display = 'block';
			document.querySelector("div[data-comment-id='" + element.getAttribute("data-comment-id") + "'] input[name='name']").focus();
		};
	});
	document.querySelectorAll(".comments .write_comment form").forEach(element => {
		element.onsubmit = event => {
			event.preventDefault();
			fetch("comments.php?page_id=" + comments_page_id, {
				method: 'POST',
				body: new FormData(element)
			}).then(response => response.text()).then(data => {
				element.parentElement.innerHTML = data;
			});
		};
	});
});
</script>

The JavaScript code above will retrieve the comments using the fetch API and append them to the comments container.

JavaScript Tip The fetch API interface can retrieve an external resource using asynchronous communication and therefore isn't render-blocking (which can impact site speed).

If you're going to add comments on a different page, you'll need to update the comments_page_id variable, as the number should be unique on every page unless you plan to output the same comments on all pages.

And now, if we navigate to this file in our web browser (over localhost), we should see something like the following:

http://localhost/phpcomments/index.html
PHP Comments System

Remember to test the commenting system features and ensure everything is working as it should.

Conclusion

Congratulations! You've successfully created a commenting system with PHP, MySQL, and AJAX. What next? Consider implementing your own features into the system and integrating it with your own projects.

There are a few benefits to using an AJAX-powered commenting system, one being it isn't going to slow down the speed of your pages, which is an important factor in SEO. The comments are dynamically rendered when the page has loaded.

Feel free to share this article and drop a comment if you've enjoyed it.