AJAX PHP

JA JASWANT Updated 18 Oct 2026
6 min read ·Lesson 110 of 143
In this post

PHP Endpoints for AJAX Requests In simple terms: In an AJAX feature, JavaScript in the browser asks a PHP script a question — "Is this username available?", "Save this comment" — and the PHP script an

PHP Endpoints for AJAX Requests

In simple terms: In an AJAX feature, JavaScript in the browser asks a PHP script a question — "Is this username available?", "Save this comment" — and the PHP script answers with data, usually JSON. The PHP side is an endpoint: a script that reads the request, does its work, and responds with a status code and a small data structure instead of an HTML page.

Technically: PHP receives AJAX requests like any other HTTP request. Query parameters arrive in $_GET; form-encoded or FormData bodies arrive in $_POST; JSON bodies must be read from php://input and decoded. The endpoint validates the input, checks permissions, and returns JSON with an appropriate HTTP status code.


A Reusable JSON Response Helper

<?php
// src/json-response.php
function jsonResponse(int $status, array $data): never
{
    http_response_code($status);
    header('Content-Type: application/json; charset=utf-8');
    header('Cache-Control: no-store');

    echo json_encode($data, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE);
    exit;
}

Every endpoint in this section uses this helper. The never return type documents that it ends the script (see Mixed and Never Types).


GET Requests: Checking a Username

The Validation Logic

Keeping the logic in a function makes it testable without a browser:

<?php
function checkUsername(string $input, array $takenUsernames): array
{
    $username = strtolower(trim($input));

    if (!preg_match('/^[a-z0-9_]{3,20}$/', $username)) {
        return ['available' => false, 'message' => 'Use 3–20 letters, digits, or underscores.'];
    }

    if (in_array($username, $takenUsernames, true)) {
        return ['available' => false, 'message' => 'This username is already taken.'];
    }

    return ['available' => true, 'message' => 'Available!'];
}

$taken = ['asha', 'bala', 'admin'];

foreach (['ab', 'Asha', 'Nila_K', 'admin!'] as $candidate) {
    echo str_pad($candidate, 8) . json_encode(checkUsername($candidate, $taken), JSON_UNESCAPED_UNICODE) . "\n";
}

Output

ab      {"available":false,"message":"Use 3–20 letters, digits, or underscores."}
Asha    {"available":false,"message":"This username is already taken."}
Nila_K  {"available":true,"message":"Available!"}
admin!  {"available":false,"message":"Use 3–20 letters, digits, or underscores."}

The Endpoint: api/check-username.php

<?php
declare(strict_types=1);

require __DIR__ . '/../src/json-response.php';
require __DIR__ . '/../src/check-username.php';   // the function above
require __DIR__ . '/../src/pdo.php';

$input = $_GET['username'] ?? '';

if (!is_string($input)) {
    jsonResponse(400, ['error' => 'Invalid request.']);
}

$taken = pdo()->query('SELECT LOWER(username) FROM users')->fetchAll(PDO::FETCH_COLUMN);

jsonResponse(200, checkUsername($input, $taken));

(This assumes a users table with a username column. In a real application, query only the requested name — SELECT 1 FROM users WHERE username = ? — instead of loading all usernames. The list form is used here only to reuse the tested function directly.)

The JavaScript: Checking While Typing

<label for="username">Username</label>
<input id="username" name="username" autocomplete="username">
<p id="username-status" aria-live="polite"></p>

<script>
    const input = document.getElementById('username');
    const status = document.getElementById('username-status');
    let timer;

    input.addEventListener('input', () => {
        clearTimeout(timer);

        timer = setTimeout(async () => {
            const params = new URLSearchParams({ username: input.value });

            try {
                const response = await fetch(`api/check-username.php?${params}`);
                if (!response.ok) {
                    throw new Error(`HTTP ${response.status}`);
                }
                const result = await response.json();
                status.textContent = result.message;
                status.className = result.available ? 'ok' : 'error';
            } catch {
                status.textContent = 'Could not check the username right now.';
            }
        }, 300);
    });
</script>

How It Works

  1. Each keystroke restarts a 300 ms timer (debouncing), so the server is asked only when the user pauses typing, not for every key.
  2. URLSearchParams builds a correctly encoded query string (username=Nila_K).
  3. The PHP endpoint reads $_GET['username'], validates it, and returns JSON.
  4. The response message is displayed with textContent.

The availability check improves the user experience, but registration must still validate the username again on the server and rely on a UNIQUE index — another user could take the name in the meantime.


POST Requests With FormData

Sending a form's fields with FormData produces a normal form submission body, so PHP reads it from $_POST — exactly like a regular form:

const form = document.getElementById('comment-form');

form.addEventListener('submit', async (event) => {
    event.preventDefault();

    const response = await fetch(form.action, {
        method: 'POST',
        body: new FormData(form),   // includes the hidden CSRF token field
    });

    const result = await response.json();
    // show result.message or the validation errors
});

This approach is ideal for progressive enhancement: without JavaScript, the form submits normally to the same endpoint; with JavaScript, it is sent in the background. The PHP script can decide which response to give based on the Accept header (application/json vs HTML).


POST Requests With a JSON Body

APIs often receive JSON. PHP does not fill $_POST for JSON bodies; read and decode the raw input:

<?php
// api/subscribe.php
declare(strict_types=1);

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

session_start();

if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') {
    jsonResponse(405, ['error' => 'Use POST.']);
}

$sessionToken = $_SESSION['csrf_token'] ?? '';
$sentToken = $_SERVER['HTTP_X_CSRF_TOKEN'] ?? '';

if ($sessionToken === '' || !is_string($sentToken) || !hash_equals($sessionToken, $sentToken)) {
    jsonResponse(419, ['error' => 'Session expired. Please reload the page.']);
}

try {
    $data = json_decode(file_get_contents('php://input'), true, 16, JSON_THROW_ON_ERROR);
} catch (JsonException) {
    jsonResponse(400, ['error' => 'Invalid JSON.']);
}

$email = is_array($data) && is_string($data['email'] ?? null) ? trim($data['email']) : '';

if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
    jsonResponse(422, ['errors' => ['email' => 'Please enter a valid email address.']]);
}

// ... save the subscription ...

jsonResponse(201, ['message' => 'Thanks for subscribing!']);
const response = await fetch('api/subscribe.php', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content,
    },
    body: JSON.stringify({ email: emailInput.value }),
});

const result = await response.json();

if (response.status === 201) {
    message.textContent = result.message;
} else if (response.status === 422) {
    message.textContent = result.errors.email;
} else {
    message.textContent = result.error ?? 'Something went wrong.';
}

Step-by-Step Explanation

  1. The endpoint accepts only POST, because it changes data.
  2. A CSRF token is sent in a custom header and compared with the session token using hash_equals(). The check fails if the session has no token at all — otherwise an empty header would match an empty session value. The token is placed in the page, for example as <meta name="csrf-token" content="...">.
  3. The JSON body is read from php://input and decoded with a depth limit and JSON_THROW_ON_ERROR.
  4. The input is validated like any form input; validation errors are returned with status 422 and a field-specific structure the JavaScript can display.
  5. Success returns status 201 Created.

Session cookies are sent automatically with same-origin fetch() requests, so $_SESSION works in AJAX endpoints just as in normal pages.


Where Request Data Arrives

JavaScript SendsPHP Reads
Query string (?q=lamp)$_GET['q']
FormData or application/x-www-form-urlencoded body$_POST, $_FILES
JSON body (application/json)json_decode(file_get_contents('php://input'), true)
Custom headers (X-CSRF-Token)$_SERVER['HTTP_X_CSRF_TOKEN']

Common Mistakes

Expecting JSON Bodies in $_POST

Effect: $_POST is empty.

Fix: Read and decode php://input.

Trusting Client-Side Checks

Effect: Invalid or malicious data reaches the database when requests are sent directly.

Fix: Validate everything on the server.

State-Changing GET Requests

Effect: Actions can be triggered by links, prefetching, or other sites.

Fix: Use POST with CSRF protection.

Returning 200 for Errors

Effect: JavaScript cannot distinguish failures from success.

Fix: Use appropriate status codes (400, 401, 403, 404, 422, 500).

Sending a Request on Every Keystroke

Effect: Unnecessary server load and out-of-order responses.

Fix: Debounce input events (and cancel outdated requests — see AJAX Live Search).


Best Practices

  • Write endpoints that validate input, check permissions, and return JSON with proper status codes.
  • Keep business logic in testable functions or classes; keep endpoints thin.
  • Use GET for reading data and POST with CSRF tokens for changes.
  • Read JSON bodies from php://input with error handling and depth limits.
  • Debounce frequent requests and handle errors gracefully in JavaScript.

Official reference: https://www.php.net/manual/en/reserved.variables.get.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 PHP and get answers drawn from it.

Signed-in readers only.