AJAX PHP

Writing a JSON endpoint in PHP and consuming it with fetch.

Beginner
Key PointsAn endpoint emits JSON only, never HTML, and must set `Content-Type`.

The endpoint

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

header("Content-Type: application/json; charset=utf-8");
header("X-Content-Type-Options: nosniff");

function respond(array $data, int $status = 200): never {
    http_response_code($status);
    echo json_encode($data, JSON_UNESCAPED_UNICODE);
    exit;
}

$name = trim((string)($_GET["name"] ?? ""));

if ($name === "") {
    respond(["ok" => false, "msg" => "name 不能为空"], 400);
}

if (mb_strlen($name) > 20) {
    respond(["ok" => false, "msg" => "name 太长"], 422);
}

respond([
    "ok"   => true,
    "msg"  => "你好,{$name}",
    "time" => "2023-11-15 06:13:20",
]);

A normal request ?name=Tom returns:

Output
{"ok":true,"msg":"你好,Tom","time":"2023-11-15 06:13:20"}

An empty parameter returns HTTP 400:

Output
{"ok":false,"msg":"name 不能为空"}
Declaring never (8.1+) tells static analysers the function never returns because it calls exit — removing false positives downstream.

The client

JavaScript
async function greet(name) {
  const url = "/api/greet.php?name=" + encodeURIComponent(name);
  const res = await fetch(url, { headers: { "Accept": "application/json" } });

  const data = await res.json();      // 4xx 也有 JSON body

  if (!data.ok) {
    document.querySelector("#err").textContent = data.msg;
    return;
  }

  document.querySelector("#out").textContent = data.msg;
}

document.querySelector("#btn").addEventListener("click", () => {
  greet(document.querySelector("#name").value);
});

The key: **parse the JSON first, then check data.ok**. A 400 response still carries a JSON body, so branching on res.ok alone loses the message.

POST with a JSON body

JavaScript
const res = await fetch("/api/user.php", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ name: "Tom", age: 18 }),
});
const data = await res.json();
console.log(data);
PHP
<?php
// /api/user.php
header("Content-Type: application/json; charset=utf-8");

if ($_SERVER["REQUEST_METHOD"] !== "POST") {
    http_response_code(405);
    echo json_encode(["ok" => false, "msg" => "只接受 POST"]);
    exit;
}

$raw  = file_get_contents("php://input");
$data = json_decode($raw, true);

if (!is_array($data)) {
    http_response_code(400);
    echo json_encode(["ok" => false, "msg" => "非法 JSON"]);
    exit;
}

echo json_encode([
    "ok"    => true,
    "name"  => $data["name"] ?? "",
    "age"   => (int)($data["age"] ?? 0),
], JSON_UNESCAPED_UNICODE);
Output
{"ok":true,"name":"Tom","age":18}
With a JSON body $_POST is empty — read php://input. The most common trap in PHP API work with Vue/React.

API conventions

ConcernPractice
状态码200 成功、400 参数错、401 未登录、403 无权限、404 不存在、422 校验失败、500 服务错
响应体固定 {ok, msg, data} 结构
字符集application/json; charset=utf-8
错误信息给人看的话,不含堆栈和 SQL
鉴权Authorization: Bearer <token>,别用 Cookie 传 token

Friends & Partners