AJAX Database

JA JASWANT Updated 18 Oct 2026
5 min read ·Lesson 111 of 143
In this post

Loading Database Data Without Reloading the Page In simple terms: Many interactive features show database information on demand: choosing a category loads its products into a second drop-down, selecti

Loading Database Data Without Reloading the Page

In simple terms: Many interactive features show database information on demand: choosing a category loads its products into a second drop-down, selecting a product shows its price and stock, clicking "Load more" fetches the next orders. With AJAX, JavaScript requests exactly the data it needs from a PHP endpoint, and PHP reads it from the database and returns it as JSON.

Technically: The pattern combines the pieces from earlier lessons: a PHP endpoint that validates request parameters, queries the database with prepared statements (see PDO Prepared Statements), maps the rows to a deliberate JSON structure, and returns it with proper status codes; and JavaScript that calls the endpoint with fetch() and updates the page safely.

The examples use the pdo() helper from PDO Connect, the jsonResponse() helper from AJAX PHP, and the shop's products table (current stock: Gel Pen 118, Notebook A5 44, Desk Lamp 6, Backpack 0, Wireless Mouse 30).


Shaping the JSON Response

Never send database rows straight to the browser. Decide which fields the client may see and in what format:

<?php
// src/product-json.php
function productJson(array $row): array
{
    return [
        'sku' => $row['sku'],
        'name' => $row['name'],
        'price' => $row['price'],
        'price_formatted' => '₹' . number_format((float) $row['price'], 2),
        'stock' => (int) $row['stock'],
        'in_stock' => (int) $row['stock'] > 0,
    ];
}

echo json_encode(
    productJson(['sku' => 'LAMP-01', 'name' => 'Desk Lamp', 'price' => '1299.00', 'stock' => 6, 'cost_price' => '820.00']),
    JSON_UNESCAPED_UNICODE
) . "\n";

Output

{"sku":"LAMP-01","name":"Desk Lamp","price":"1299.00","price_formatted":"₹1,299.00","stock":6,"in_stock":true}

Why a Mapping Function?

  • Internal columns (here, a hypothetical cost_price, or user password hashes, internal notes) never leak into public responses — even if the query or table changes later.
  • Types are explicit: stock is a number and in_stock a boolean in JSON.
  • The client receives display-ready values (price_formatted) while keeping the exact decimal string for calculations.
  • The function can be tested without a database or browser.

The Product Endpoint: api/product.php

<?php
declare(strict_types=1);

require __DIR__ . '/../src/pdo.php';
require __DIR__ . '/../src/json-response.php';
require __DIR__ . '/../src/product-json.php';

$sku = $_GET['sku'] ?? '';

if (!is_string($sku) || !preg_match('/^[A-Z0-9-]{3,30}$/', $sku = strtoupper(trim($sku)))) {
    jsonResponse(400, ['error' => 'Invalid SKU.']);
}

$stmt = pdo()->prepare('SELECT sku, name, price, stock FROM products WHERE sku = ? AND is_active = 1');
$stmt->execute([$sku]);
$row = $stmt->fetch();

if ($row === false) {
    jsonResponse(404, ['error' => 'Product not found.']);
}

jsonResponse(200, productJson($row));

Responses

RequestStatusBody
api/product.php?sku=lamp-01200{"sku":"LAMP-01","name":"Desk Lamp","price":"1299.00","price_formatted":"₹1,299.00","stock":6,"in_stock":true}
api/product.php?sku=BAG-02200{"sku":"BAG-02","name":"Backpack","price":"899.00","price_formatted":"₹899.00","stock":0,"in_stock":false}
api/product.php?sku=NOPE-99404{"error":"Product not found."}
api/product.php?sku=<script>400{"error":"Invalid SKU."}

How It Works

  1. The SKU is normalized to uppercase and validated against a strict pattern before any database access — invalid input gets a 400 response.
  2. A prepared statement fetches the product; only active products are visible.
  3. A missing product returns 404, so JavaScript can distinguish "not found" from success.
  4. The row is shaped by productJson() and sent as JSON.

The Category Endpoint: api/products-by-category.php

<?php
declare(strict_types=1);

require __DIR__ . '/../src/pdo.php';
require __DIR__ . '/../src/json-response.php';

$allowedCategories = ['stationery', 'electronics', 'bags'];
$category = $_GET['category'] ?? '';

if (!in_array($category, $allowedCategories, true)) {
    jsonResponse(400, ['error' => 'Unknown category.']);
}

$stmt = pdo()->prepare('SELECT sku, name FROM products WHERE category = ? AND is_active = 1 ORDER BY name');
$stmt->execute([$category]);

jsonResponse(200, ['products' => $stmt->fetchAll()]);

Response for ?category=electronics

{"products":[{"sku":"LAMP-01","name":"Desk Lamp"},{"sku":"MOUSE-01","name":"Wireless Mouse"}]}

Returning an object with a products key (instead of a bare array) leaves room to add fields such as total or next_page later without breaking clients.


The Page: Dependent Drop-Downs and a Details Panel

<label>Category
    <select id="category">
        <option value="">Choose…</option>
        <option value="stationery">Stationery</option>
        <option value="electronics">Electronics</option>
        <option value="bags">Bags</option>
    </select>
</label>

<label>Product
    <select id="product" disabled></select>
</label>

<div id="details" aria-live="polite"></div>

<script>
    const categorySelect = document.getElementById('category');
    const productSelect = document.getElementById('product');
    const details = document.getElementById('details');

    async function getJson(url) {
        const response = await fetch(url, { headers: { Accept: 'application/json' } });
        const data = await response.json();
        if (!response.ok) {
            throw new Error(data.error ?? `HTTP ${response.status}`);
        }
        return data;
    }

    categorySelect.addEventListener('change', async () => {
        productSelect.replaceChildren(new Option('Loading…', ''));
        productSelect.disabled = true;
        details.textContent = '';

        if (categorySelect.value === '') {
            productSelect.replaceChildren();
            return;
        }

        try {
            const params = new URLSearchParams({ category: categorySelect.value });
            const { products } = await getJson(`api/products-by-category.php?${params}`);

            productSelect.replaceChildren(
                new Option('Choose a product…', ''),
                ...products.map(product => new Option(product.name, product.sku))
            );
            productSelect.disabled = false;
        } catch (error) {
            productSelect.replaceChildren();
            details.textContent = error.message;
        }
    });

    productSelect.addEventListener('change', async () => {
        if (productSelect.value === '') {
            details.textContent = '';
            return;
        }

        try {
            const params = new URLSearchParams({ sku: productSelect.value });
            const product = await getJson(`api/product.php?${params}`);

            details.textContent = product.in_stock
                ? `${product.name}: ${product.price_formatted} — ${product.stock} in stock`
                : `${product.name}: ${product.price_formatted} — currently out of stock`;
        } catch (error) {
            details.textContent = error.message;
        }
    });
</script>

Result in the Browser

Choosing Electronics fills the second drop-down with "Desk Lamp" and "Wireless Mouse". Choosing Desk Lamp then shows:

Desk Lamp: ₹1,299.00 — 6 in stock

How It Works

  1. getJson() centralizes fetching: it parses the JSON and turns error responses into exceptions carrying the server's error message.
  2. new Option(text, value) creates <option> elements safely — product names are inserted as text, never as HTML.
  3. replaceChildren() replaces all options at once.
  4. The product drop-down is disabled while loading, preventing selections from an outdated list.

Performance and Security Considerations

ConcernRecommendation
Many small requestsReturn everything a view needs in one response; avoid one request per row
Repeated identical requestsCache in the browser (Cache-Control: max-age=60 for public catalog data) or on the server
Large result setsPaginate (see Pagination with PHP)
Private dataCheck the session and permissions in every endpoint; never trust IDs sent by JavaScript alone
AbuseRate-limit expensive endpoints, validate all parameters strictly
Database userRead-only endpoints can use a database user with SELECT privileges only

Common Mistakes

Returning Raw Database Rows

Effect: Internal or sensitive columns are exposed.

Fix: Map rows to explicit response structures.

Building Queries From Request Parameters

Effect: SQL injection.

Fix: Validate input and use prepared statements.

Inserting Data With innerHTML

Effect: XSS through product names or other stored text.

Fix: Use textContent, new Option(), or DOM methods.

Not Handling Errors in JavaScript

Effect: The page silently stops working.

Fix: Check response.ok and display error messages.


Best Practices

  • Keep endpoints small: validate, query with prepared statements, map, respond.
  • Return deliberate JSON structures with correct types and status codes.
  • Validate parameters against allow-lists or strict patterns.
  • Render data safely on the client and handle loading and error states.
  • Paginate, cache, and rate-limit where appropriate.

Official reference: https://www.php.net/manual/en/book.mysqli.php

0 Comments

Reviewed before they appear

No comments yet.

PHP
PHP Home PHP Switch PHP Match Alternative Syntax PHP Loops While Loop Do While Loop For Loop Foreach Loop Nested Loops Break Statement PHP Home PHP Intro PHP Install PHP Built-in Web Server PHP Syntax PHP Tags PHP and HTML PHP Comments PHP Multiline Comments PHP Variables Variables Scope PHP Casting Logical Operators Null Coalescing Operator Spaceship Operator Operator Precedence Continue Statement Modify Strings PHP If...Else...Elseif PHP If Modify Strings PHP If Operators PHP Functions Concatenate Strings PHP If...Else Slicing Strings User-defined Functions Slicing Strings PHP Shorthand if Escape Characters Default Argument Values PHP Nested if Pass by Reference String Interpolation Named Arguments PHP File Upload Variadic Functions Return Values Search in Strings Multibyte Strings Type Declarations in Functions PHP Numbers PHP Math PHP Floats PHP Random Numbers PHP Constants PHP Properties PHP Methods PHP Constructor Constructor Property Promotion PHP Cookies PHP Destructor PHP Access Modifiers Readonly Properties PHP Sessions PHP Inheritance Method Overriding PHP Filters The final Keyword CSRF Protection PHP Filters Advanced Session Security Secure Cookies PHP Magic Constants File Upload Security PHP Callback Functions PHP Operators Filesystem Security PHP JSON Arithmetic Operators Error Reporting in Production Hiding PHP Assignment Operators PHP Configuration (php.ini) Comparison Operators PHP Command Line (CLI) PHP Exceptions Increment / Decrement Operators Migrating to PHP 8 PHP Form Validation PHP Form Required PHP Form URL/E-mail Error Handling Form Input Sanitizing Escaping Output (htmlspecialchars) Custom Error Handler Checkbox, Radio and Select Inputs Sticky Forms PHP Form Complete PHP Date and Time Date Formatting Error Logging HTTP Headers and Redirects Output Buffering Sending Email PHP cURL Consuming REST APIs PHP Generators AJAX PHP AJAX Database AJAX XML AJAX Live Search AJAX Poll AJAX with JSON Security Introduction Password Hashing SQL Injection Prevention XSS Prevention Garbage Collection Project: Simple Blog PHP What is OOP PHP Classes/Objects The $this Keyword Access Array Items Update Array Items Add Array Items Remove Array Items Sorting Arrays Multidimensional Arrays Array Destructuring Array Spread Operator Search in Arrays Merge Arrays PHP Abstract Classes PHP Interfaces Polymorphism PHP Traits PHP Static Methods PHP Static Properties Late Static Binding Magic Methods Object Cloning
Ask about this post
AI Ask about this post

Ask questions about AJAX Database and get answers drawn from it.

Signed-in readers only.