Server-Side Pagination using HTML, CSS, Javascript and PHP
This tutorial explains how to implement server-side pagination using JavaScript and PHP. When dealing with large databases, loading all data at once can slow down a webpage. Pagination helps by loading only a portion of the data (data-on-demand) based on user actions on the client-side.
We begin by creating a table in MySQL database for the tutorial in . The main focus here is on a server-side implementation using PHP and MySQL in , and client-side implementation using JavaScript in . SQLdump data, sample images, live demos, and codes are provided in
Pre-requisite
HTML, CSS, Javascript, JQuery, PHP, and SQL.
MySQL database creation
We create a DEMO_TABLE in DEMO_DB database. For pagination based data-on-demand, our server-side script will fetch data from this table.
INSERT INTO `photos` (id, title, description, path) VALUES
(1,'Sunset Beach','A photo of the beach during sunset','images/1.jpg'),
(2,'Mountain View','A view of snow-capped mountains','images/2.jpg'),
(3,'City Skyline','Skyline of a city at night','images/3.jpg'),
(4,'Forest Path','Path going through a forest','images/4.jpg'),
(5,'Desert Dunes','Sand dunes under the sun','images/5.jpg'),
(6,'Waterfall','Waterfall in the mountains','images/6.jpg'),
(7,'Countryside','Countryside with green fields','images/7.jpg'),
(8,'Bridge View','Bridge over a river','images/8.jpg'),
(9,'Lake Reflection','Reflection of trees on a lake','images/9.jpg'),
(10,'Starry Night','Stars visible in the night sky','images/10.jpg'),
(11,'City Lights','Bright lights in downtown','images/11.jpg'),
(12,'Autumn Leaves','Fallen leaves in autumn','images/12.jpg'),
(13,'Foggy Morning','A foggy landscape in the morning','images/13.jpg'),
(14,'Ocean Waves','Waves crashing on rocks','images/14.jpg'),
(15,'Snow Forest','Snow covering the forest trees','images/15.jpg'),
(16,'Canyon View','Looking across a canyon','images/16.jpg'),
(17,'Old Town','Historic streets in an old town','images/17.jpg'),
(18,'Flower Field','Wide field full of flowers','images/18.jpg'),
(19,'Boat Ride','A boat sailing on the water','images/19.jpg'),
(20,'Night Bridge','Bridge illuminated at night','images/20.jpg'),
(21,'Rainy Street','A wet street after rain','images/21.jpg'),
(22,'Winter Cabin','Cabin in the snowy woods','images/22.jpg'),
(23,'Golden Gate','Famous red suspension bridge','images/23.jpg'),
(24,'City Park','Green park inside a city','images/24.jpg'),
(25,'Hot Air Balloons','Balloons flying in the sky','images/25.jpg'),
(26,'Harbor Boats','Boats docked at the harbor','images/26.jpg'),
(27,'Desert Road','Straight road through desert','images/27.jpg'),
(28,'Rocky Shore','Shore with large rocks','images/28.jpg'),
(29,'Sunrise Hills','Sun rising over hills','images/29.jpg'),
(30,'Lighthouse','Lighthouse on the coast','images/30.jpg'),
(31,'City Market','Street market with stalls','images/31.jpg'),
(32,'Rural Barn','A barn in the countryside','images/32.jpg'),
(33,'Palm Beach','Palm trees near the ocean','images/33.jpg'),
(34,'Busy Crosswalk','People crossing the street','images/34.jpg'),
(35,'Mountain Lake','Lake surrounded by mountains','images/35.jpg'),
(36,'Green Valley','Valley covered with grass','images/36.jpg'),
(37,'Street Art','Wall covered in graffiti','images/37.jpg'),
(38,'Clock Tower','Clock tower in the square','images/38.jpg'),
(39,'Winter River','Frozen river during winter','images/39.jpg'),
(40,'Train Tracks','Tracks leading to distance','images/40.jpg'),
(41,'Camping Site','Tent set up in woods','images/41.jpg'),
(42,'Sunny Field','Field under bright sun','images/42.jpg'),
(43,'Snowy Peak','Sharp mountain peak covered with snow','images/43.jpg'),
(44,'Bridge Tunnel','View through a tunnel under a bridge','images/44.jpg'),
(45,'Fishing Dock','People fishing off a dock','images/45.jpg'),
(46,'Cloudy Sky','Thick clouds covering sky','images/46.jpg'),
(47,'Ancient Temple','Ruins of an ancient temple','images/47.jpg'),
(48,'Field Road','Small road cutting through fields','images/48.jpg'),
(49,'Island View','Tropical island in the distance','images/49.jpg'),
(50,'Pier Walk','Wooden pier into the water','images/50.jpg');
Server-side script for supplying data-on-demand
To implement pagination, the backend must be set up to send specific data to the client. In this tutorial, we use MySQL as our database. In SQL, one needs a start index (offset) and a limit to determine how much data to fetch. Here's a server-side PHP code that sends data-on-demand to the client side.
<?php
/**
* server.php
* Pagination API returning JSON (success or error)
*/
declare(strict_types=1);
// Force JSON response for everything
header('Content-Type: application/json; charset=utf-8');
// Database config
const DB_HOST = "localhost";
const DB_USER = "demo_user";
const DB_PASS = "str0ng_passw0rd";
const DB_NAME = "pagination_demo";
// Connect to DB
$conn = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME);
if ($conn->connect_error) {
http_response_code(500);
echo json_encode(['error' => 'Database connection failed: ' . $conn->connect_error]);
exit;
}
// Get request params
$limit = isset($_GET['limit']) && is_numeric($_GET['limit']) ? max(1, intval($_GET['limit'])) : 10;
$page = isset($_GET['page']) && is_numeric($_GET['page']) ? max(0, intval($_GET['page'])) : 0;
$offset = $page * $limit;
// Count total rows
$result_count = $conn->query("SELECT COUNT(*) AS total_data FROM photos");
if (!$result_count) {
http_response_code(500);
echo json_encode(['error' => 'Failed to count photos: ' . $conn->error]);
$conn->close();
exit;
}
$total_rows = (int)$result_count->fetch_assoc()['total_data'];
$total_pages = (int)ceil($total_rows / $limit);
// Fetch paginated rows
$stmt = $conn->prepare("SELECT id, title, description, photo_url FROM photos LIMIT ?, ?");
if (!$stmt) {
http_response_code(500);
echo json_encode(['error' => 'Failed to prepare query: ' . $conn->error]);
$conn->close();
exit;
}
$stmt->bind_param("ii", $offset, $limit);
$stmt->execute();
$result = $stmt->get_result();
// Build response
$photos = [];
while ($row = $result->fetch_assoc()) {
$photos[] = [
'id' => (int)$row['id'],
'title' => $row['title'],
'description' => $row['description'],
'url' => 'https://soln.tech/images/picsum/'.$row['photo_url'],
];
}
// Output JSON
echo json_encode([
'total_pages' => $total_pages,
'photos' => $photos
]);
// Cleanup
$stmt->close();
$conn->close();
?>
Client-side pagination using Javascript
JQuery to make AJAX Call to the Server
After setting up the backend for pagination, the next step is to build the client-side using HTML, CSS, and JavaScript. jQuery is used to keep the code simple and readable. An AJAX call is made to fetch data for the selected page from the server. We implement this inside renderData function.
renderData(page) {
$.ajax({
url: this.apiUrl,
type: "GET",
data: { page: page, limit: this.limit },
success: (response) => {
if (response.error) {
console.error("Server error:", response.error);
this.showError(response.error);
return;
}
this.totalPages = response.total_pages || 0;
this.renderCards(response.photos || []);
this.renderPagination();
},
error: (xhr, status, error) => {
console.error("AJAX error:", error);
this.showError("Failed to fetch data from server.");
}
});
}
Update data in the HTML container
The renderData sends a GET request to the server.php file on the server, passing the selected page number and limit as a parameter. Once the server responds with the relevant data, the renderCards function updates data inside the HTML element. This allows the webpage to update the displayed content without needing to reload the entire page.
renderCards(photos) {
if (!photos.length) {
this.dataContainer.html("<p class='text-muted'>No photos available.</p>");
return;
}
const html = photos.map(photo => `
<div class="col-lg-4 col-md-6 mb-4">
<div class="card shadow rounded overflow-hidden">
<img class="img-fluid"
style="width:100%;height:250px;object-fit:cover;"
src="${photo.url}"
alt="${photo.title}">
<div class="card-body">
<h5 class="card-title">${photo.title}</h5>
<p class="card-text">${photo.description}</p>
</div>
</div>
</div>
`).join("");
this.dataContainer.html(html);
}
Pagination buttons
In the previous section, we implemented an AJAX call to the server to fetch data for a selected page and update the HTML container. Now, we add the logic to navigate between pages using JavaScript. This involves the following steps:
- Calculate the total number of pages using the size of the dataset and the number of items shown per page. Generate buttons for each page.
- When a page button is clicked, trigger an AJAX call to the server to fetch and display the data for that page.
- Update the state of the buttons by highlighting the active page or disabling buttons where necessary.
renderPagination() {
if (this.totalPages <= 1) {
this.paginationTop.empty();
this.paginationBottom.empty();
return;
}
let html = ``;
for (let i = 0; i < this.totalPages; i++) {
const isActive = (i === this.page) ? "btn-dark active" : "btn-outline-dark";
html += `
<li class="page-item">
<button class="btn ${isActive} page-btn" data-page="${i}">${i + 1}</button>
</li>`;
}
this.paginationTop.html(html);
this.paginationBottom.html(html);
}
bindEvents() {
$(document).on("click", ".page-btn", (e) => {
this.page = parseInt($(e.currentTarget).data("page"));
this.renderData(this.page);
});
}
Complete Javascript code for the client-side
We also implement an updateUrl function to add the current page number as a parameter in the url. This facilitates user to save the link to revisit a particular page.
The updateURL(page) function updates the browser's address bar to reflect the current page number without reloading the webpage. It first creates a URL object using the current page URL. Then it uses searchParams.set('page', page) to either add or update the page parameter in the query string. Finally, it calls history.replaceState() to change the URL in the browser’s address bar without triggering a full page reload. This allows users to bookmark or share the exact page they’re viewing in a paginated interface. One should call this function whenever a pagination button is pressed and also the first time the webpage is loaded.
class PhotoGallery {
constructor(config) {
this.page = 0;
this.limit = config.limit || 6;
this.totalPages = 0;
this.apiUrl = config.apiUrl;
this.dataContainer = $(config.dataContainer);
this.paginationTop = $(config.paginationTop);
this.paginationBottom = $(config.paginationBottom);
this.init();
}
init() {
this.bindEvents();
this.renderData(this.page);
}
bindEvents() {
$(document).on("click", ".page-btn", (e) => {
this.page = parseInt($(e.currentTarget).data("page"));
this.renderData(this.page);
});
}
renderData(page) {
$.ajax({
url: this.apiUrl,
type: "GET",
data: { page: page, limit: this.limit },
success: (response) => {
if (response.error) {
console.error("Server error:", response.error);
this.showError(response.error);
return;
}
this.totalPages = response.total_pages || 0;
this.renderCards(response.photos || []);
this.renderPagination();
},
error: (xhr, status, error) => {
console.error("AJAX error:", error);
this.showError("Failed to fetch data from server.");
}
});
}
renderCards(photos) {
if (!photos.length) {
this.dataContainer.html("<p class='text-muted'>No photos available.</p>");
return;
}
const html = photos.map(photo => `
<div class="col-lg-4 col-md-6 mb-4">
<div class="card shadow rounded overflow-hidden">
<img class="img-fluid"
style="width:100%;height:250px;object-fit:cover;"
src="${photo.url}"
alt="${photo.title}">
<div class="card-body">
<h5 class="card-title">${photo.title}</h5>
<p class="card-text">${photo.description}</p>
</div>
</div>
</div>
`).join("");
this.dataContainer.html(html);
}
renderPagination() {
if (this.totalPages <= 1) {
this.paginationTop.empty();
this.paginationBottom.empty();
return;
}
let html = ``;
for (let i = 0; i < this.totalPages; i++) {
const isActive = (i === this.page) ? "btn-dark active" : "btn-outline-dark";
html += `
<li class="page-item">
<button class="btn ${isActive} page-btn" data-page="${i}">${i + 1}</button>
</li>`;
}
this.paginationTop.html(html);
this.paginationBottom.html(html);
}
showError(message) {
this.dataContainer.html(`<p class='text-danger fw-bold'>${message}</p>`);
this.paginationTop.empty();
this.paginationBottom.empty();
}
}
// Initialize when document is ready
$(document).ready(() => {
new PhotoGallery({
apiUrl: "server.php",
dataContainer: "#data",
paginationTop: "#pagination-top",
paginationBottom: "#pagination-bottom",
limit: 6
});
});
Live demos and codes for pagination
Sample images and sqldump are common to all versions of pagination implementaion.Version 1
Version 2
Version 3
Version 4
Author
Anurag Gupta is an M.S. graduate in Electrical and Computer Engineering from Cornell University. He also holds an M.Tech degree in Systems and Control Engineering and a B.Tech degree in Electrical Engineering from the Indian Institute of Technology, Bombay.
Comment
This policy contains information about your privacy. By posting, you are declaring that you understand this policy:
- Your name, rating, website address, town, country, state and comment will be publicly displayed if entered.
- Aside from the data entered into these form fields, other stored data about your comment will include:
- Your IP address (not displayed)
- The time/date of your submission (displayed)
- Your email address will not be shared. It is collected for only two reasons:
- Administrative purposes, should a need to contact you arise.
- To inform you of new comments, should you subscribe to receive notifications.
- A cookie may be set on your computer. This is used to remember your inputs. It will expire by itself.
This policy is subject to change at any time and without notice.
These terms and conditions contain rules about posting comments. By submitting a comment, you are declaring that you agree with these rules:
- Although the administrator will attempt to moderate comments, it is impossible for every comment to have been moderated at any given time.
- You acknowledge that all comments express the views and opinions of the original author and not those of the administrator.
- You agree not to post any material which is knowingly false, obscene, hateful, threatening, harassing or invasive of a person's privacy.
- The administrator has the right to edit, move or remove any comment for any reason and without notice.
Failure to comply with these rules may result in being banned from submitting further comments.
These terms and conditions are subject to change at any time and without notice.
Similar content
Past Comments