AJAX JSON là gì? Cách trao đổi và xử lý dữ liệu JSON

Trong các website hiện đại, trình duyệt không phải lúc nào cũng cần tải lại toàn bộ trang mỗi khi người dùng thực hiện một thao tác. Khi nhập từ khóa tìm kiếm, đăng nhập, thêm sản phẩm vào giỏ hàng, lọc dữ liệu hoặc tải thêm nội dung, website có thể gửi một yêu cầu nhỏ đến server và nhận về đúng phần dữ liệu cần thiết. Hai thành phần thường xuất hiện trong quá trình này là AJAXJSON.

AJAX chịu trách nhiệm giúp JavaScript giao tiếp với server mà không cần tải lại toàn bộ trang, còn JSON thường được sử dụng làm định dạng để đóng gói dữ liệu trong quá trình trao đổi. Vì vậy, khi nói đến AJAX JSON, có thể hiểu đơn giản đây là mô hình trong đó JavaScript gửi request đến server, server xử lý và trả dữ liệu về dưới dạng JSON để JavaScript tiếp tục xử lý trên giao diện.

Điểm quan trọng là AJAX và JSON không phải một công nghệ duy nhất. AJAX là cách thức giao tiếp bất đồng bộ giữa client và server, trong khi JSON là một định dạng dữ liệu. Hiểu rõ vai trò riêng của từng thành phần sẽ giúp việc xây dựng website bằng JavaScript, PHP và các công nghệ phía server dễ kiểm soát hơn.

AJAX JSON là gì? Cách trao đổi và xử lý dữ liệu JSON
AJAX JSON là gì? Cách trao đổi và xử lý dữ liệu JSON

AJAX và JSON đóng vai trò gì trong quá trình giao tiếp?

Để hiểu đúng AJAX JSON, trước tiên cần tách hai khái niệm này ra. AJAX là viết tắt của Asynchronous JavaScript and XML. Tên gọi xuất hiện từ thời XML được sử dụng phổ biến để trao đổi dữ liệu, nhưng trên thực tế AJAX ngày nay không bắt buộc phải dùng XML. JSON mới là định dạng được sử dụng rất rộng rãi vì cấu trúc gọn, dễ đọc và thuận tiện cho JavaScript.

AJAX cho phép JavaScript tạo request HTTP tới server ở phía sau giao diện đang hiển thị. Khi server hoàn thành xử lý, response được trả về và JavaScript có thể cập nhật một phần giao diện thay vì yêu cầu trình duyệt tải lại toàn bộ tài liệu HTML.

JSON đảm nhận một nhiệm vụ khác: biểu diễn dữ liệu. Chẳng hạn, server có thể trả về thông tin một sản phẩm gồm tên, giá, hình ảnh và trạng thái tồn kho. Thay vì trả nguyên một trang HTML, server có thể gửi một đối tượng JSON chứa các trường dữ liệu đó.

Một quy trình AJAX JSON cơ bản thường diễn ra như sau:

  1. Người dùng thực hiện một thao tác trên giao diện.
  2. JavaScript thu thập dữ liệu cần gửi.
  3. JavaScript tạo HTTP request tới server.
  4. Server tiếp nhận request và kiểm tra dữ liệu.
  5. Server thực hiện nghiệp vụ, truy vấn cơ sở dữ liệu hoặc xử lý logic cần thiết.
  6. Server trả response, thường chứa dữ liệu JSON.
  7. JavaScript đọc JSON và cập nhật phần giao diện tương ứng.

Cách làm này đặc biệt hữu ích với những giao diện có nhiều thao tác nhỏ. Ví dụ, khi người dùng lọc danh sách sản phẩm theo mức giá, website chỉ cần gửi điều kiện lọc lên server và nhận danh sách kết quả mới. Không nhất thiết phải tải lại toàn bộ menu, header, footer và những phần giao diện không thay đổi.

AJAX không đồng nghĩa với JSON

Một nhầm lẫn phổ biến là cho rằng AJAX chính là JSON hoặc AJAX bắt buộc phải dùng JSON. Hai khái niệm này hoàn toàn khác nhau.

AJAX mô tả cách JavaScript giao tiếp với server một cách bất đồng bộ. Dữ liệu trao đổi có thể được biểu diễn bằng JSON, XML, text hoặc những định dạng khác tùy thiết kế của hệ thống.

Ngược lại, JSON có thể được sử dụng ngay cả khi không triển khai AJAX. Một file cấu hình, API hoặc một hệ thống backend khác cũng có thể tạo và đọc JSON mà không liên quan trực tiếp đến AJAX.

Trong các website hiện nay, AJAX kết hợp JSON trở nên phổ biến vì JavaScript có khả năng làm việc rất thuận tiện với dữ liệu dạng object và array. Điều này giúp dữ liệu từ server có thể nhanh chóng được chuyển thành nội dung hiển thị trên giao diện.

JSON được cấu trúc như thế nào?

JSON, viết tắt của JavaScript Object Notation, là một định dạng dữ liệu dạng văn bản. JSON không phải cơ sở dữ liệu và cũng không phải ngôn ngữ lập trình. Nó chỉ cung cấp quy tắc để biểu diễn dữ liệu theo cấu trúc mà nhiều ngôn ngữ lập trình có thể đọc và tạo ra.

Hai cấu trúc quan trọng nhất của JSON là objectarray. Object thường dùng để mô tả một thực thể có nhiều thuộc tính, còn array dùng để biểu diễn một danh sách nhiều phần tử.

Ví dụ, server có thể trả về thông tin một sản phẩm như sau:

{
  "id": 125,
  "name": "Website bán hàng",
  "price": 6000000,
  "status": "active"
}

Trong ví dụ trên, dữ liệu được tổ chức thành các cặp key và value. Key như id, name, pricestatus mô tả tên trường dữ liệu. Value là giá trị tương ứng của từng trường.

Nếu cần trả về nhiều sản phẩm, JSON có thể sử dụng array:

[
  {
    "id": 125,
    "name": "Website bán hàng",
    "price": 6000000
  },
  {
    "id": 126,
    "name": "Website doanh nghiệp",
    "price": 8000000
  }
]

Cấu trúc này rất phù hợp với AJAX. JavaScript nhận được một danh sách JSON có thể duyệt qua từng phần tử, lấy dữ liệu cần thiết và tạo HTML tương ứng trên giao diện.

Những kiểu dữ liệu JSON có thể biểu diễn

JSON hỗ trợ một số kiểu dữ liệu cơ bản gồm chuỗi, số, boolean, null, object và array. Tuy nhiên, JSON có quy tắc riêng và không thể tùy tiện viết giống cú pháp của JavaScript.

Ví dụ, giá trị boolean trong JSON phải là true hoặc false, không phải chuỗi "true" hoặc "false" nếu muốn biểu diễn đúng kiểu boolean.

Chuỗi phải được đặt trong dấu ngoặc kép. Đây cũng là một trong những điểm thường gây lỗi khi lập trình viên tự xây dựng JSON bằng cách nối chuỗi thủ công.

Ví dụ hợp lệ:

{
  "name": "Web Moi",
  "active": true,
  "price": 6000000,
  "description": null
}

Việc nắm chắc cấu trúc JSON rất quan trọng vì AJAX chỉ có thể xử lý dữ liệu chính xác khi response từ server tuân thủ đúng cú pháp. Một dấu phẩy thừa, dấu ngoặc thiếu hoặc chuỗi không được đóng đúng cách cũng có thể khiến quá trình phân tích dữ liệu thất bại.

Quy trình gửi và nhận dữ liệu bằng AJAX JSON

Một request AJAX JSON thường có hai chiều dữ liệu. Chiều thứ nhất là dữ liệu từ trình duyệt gửi lên server. Chiều thứ hai là dữ liệu server trả ngược về trình duyệt.

Ví dụ, một biểu mẫu đăng nhập có thể thu thập email và mật khẩu. JavaScript gửi hai giá trị này tới server. Server kiểm tra tài khoản, thực hiện xác thực và trả về kết quả. Response có thể được thiết kế theo dạng:

{
  "success": true,
  "message": "Đăng nhập thành công"
}

JavaScript sau đó đọc trường success để xác định kết quả và sử dụng message để hiển thị thông báo cho người dùng.

Điểm đáng chú ý là server không nhất thiết phải quyết định giao diện sẽ thay đổi như thế nào. Server tập trung trả về dữ liệu và trạng thái xử lý, còn JavaScript chịu trách nhiệm quyết định cách thể hiện kết quả trên trình duyệt. Sự phân tách này giúp hệ thống dễ bảo trì hơn khi giao diện thay đổi.

Request và response cần được thiết kế rõ ràng

Trong một hệ thống thực tế, việc thống nhất cấu trúc dữ liệu quan trọng không kém việc gửi request thành công. Nếu mỗi API trả về một kiểu JSON khác nhau, JavaScript sẽ phải xử lý rất nhiều trường hợp đặc biệt.

Một cấu trúc response có thể được thiết kế theo hướng thống nhất:

{
  "success": true,
  "message": "Thao tác thành công",
  "data": {
    "id": 125,
    "name": "Website bán hàng"
  }
}

Trong trường hợp xảy ra lỗi, server có thể trả về cấu trúc tương tự nhưng thay đổi trạng thái:

{
  "success": false,
  "message": "Dữ liệu không hợp lệ",
  "data": null
}

Cách tổ chức này giúp JavaScript dễ xác định ba vấn đề: request có thành công hay không, người dùng cần nhận thông báo gì và dữ liệu thực tế nằm ở đâu.

Tuy nhiên, trường success chỉ là quy ước do hệ thống tự thiết kế, không phải yêu cầu bắt buộc của JSON hay AJAX. Một API có thể sử dụng cấu trúc khác miễn là client và server thống nhất với nhau.

Trong các hệ thống được xây dựng bài bản, response còn cần kết hợp với HTTP status code. Ví dụ, request thành công có thể sử dụng mã 200, dữ liệu gửi lên không hợp lệ có thể sử dụng 400, chưa xác thực có thể sử dụng 401 và lỗi phía server có thể sử dụng 500. Khi đó, JavaScript không chỉ dựa vào một trường boolean trong JSON mà còn có thêm thông tin từ tầng HTTP để xử lý chính xác.

Gửi dữ liệu JSON từ JavaScript lên PHP

Khi xây dựng website bằng JavaScript và PHP, một trong những cách phổ biến nhất là sử dụng fetch() để gửi request đến một file PHP hoặc một endpoint xử lý riêng. Nếu muốn gửi dữ liệu dưới dạng JSON, JavaScript cần chuyển object thành chuỗi JSON trước khi đưa vào request body.

Ví dụ, giả sử giao diện cần gửi thông tin khách hàng gồm họ tên, email và số điện thoại:

const customer = {
  name: "Nguyen Van A",
  email: "a@example.com",
  phone: "0900000000"
};

fetch("process.php", {
  method: "POST",
  headers: {
    "Content-Type": "application/json"
  },
  body: JSON.stringify(customer)
})
.then(response => response.json())
.then(data => {
  console.log(data);
})
.catch(error => {
  console.error(error);
});

Ở đây có hai thao tác rất quan trọng. JSON.stringify() chuyển object JavaScript thành chuỗi JSON để có thể truyền qua HTTP. Ngược lại, response.json() phân tích response JSON mà server trả về thành dữ liệu JavaScript.

Header Content-Type: application/json cũng rất quan trọng. Nó thông báo cho server rằng nội dung trong request body đang được gửi theo định dạng JSON. Nếu bỏ qua thông tin này, PHP vẫn có thể nhận được dữ liệu thô nhưng việc xác định và xử lý request sẽ dễ phát sinh sai lệch, đặc biệt khi hệ thống có nhiều loại request khác nhau.

Vì sao không dùng $_POST để đọc JSON?

Đây là vấn đề khiến nhiều người mới làm AJAX JSON gặp lỗi. Khi JavaScript gửi dữ liệu bằng application/x-www-form-urlencoded hoặc multipart/form-data, PHP có thể tự đưa dữ liệu vào $_POST. Nhưng khi request body chứa JSON với Content-Type: application/json, dữ liệu không tự động xuất hiện trong $_POST.

Trong trường hợp này, PHP cần đọc dữ liệu thô từ request body:

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

Biến $rawData chứa chuỗi JSON được gửi từ trình duyệt. Hàm json_decode() chuyển chuỗi đó thành dữ liệu PHP để chương trình có thể tiếp tục xử lý.

Tham số true khiến object JSON được chuyển thành associative array. Nếu không truyền tham số này, object thường được chuyển thành object PHP.

Ví dụ sau khi giải mã:

{
  "name": "Nguyen Van A",
  "email": "a@example.com"
}

có thể được truy cập trong PHP như sau:

echo $data["name"];
echo $data["email"];

Đây là điểm khác biệt quan trọng giữa việc gửi form truyền thống và gửi JSON. Không nên mặc định rằng mọi dữ liệu POST đều nằm trong $_POST. Cách đọc dữ liệu phải phù hợp với Content-Type của request.

Xử lý dữ liệu JSON trong PHP an toàn và chính xác

Đọc được JSON mới chỉ là bước đầu. Một server tốt không nên lấy dữ liệu vừa nhận rồi lập tức đưa vào cơ sở dữ liệu hoặc thực hiện nghiệp vụ. Dữ liệu từ trình duyệt luôn phải được xem là dữ liệu chưa đáng tin cậy.

Quy trình hợp lý thường gồm: đọc request, giải mã JSON, kiểm tra cấu trúc, kiểm tra kiểu dữ liệu, xác thực giá trị, thực hiện nghiệp vụ và cuối cùng tạo response JSON.

Ví dụ đơn giản:

<?php

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

if (!is_array($data)) {
    http_response_code(400);

    echo json_encode([
        "success" => false,
        "message" => "Dữ liệu JSON không hợp lệ"
    ]);

    exit;
}

$name = trim($data["name"] ?? "");
$email = trim($data["email"] ?? "");

if ($name === "" || $email === "") {
    http_response_code(422);

    echo json_encode([
        "success" => false,
        "message" => "Vui lòng nhập đầy đủ thông tin"
    ]);

    exit;
}

Trong ví dụ này, server không giả định rằng name hoặc email chắc chắn tồn tại. Toán tử ?? giúp tránh lỗi khi key không được gửi lên.

Việc dùng trim() cũng giúp loại bỏ khoảng trắng không cần thiết ở đầu và cuối chuỗi. Tuy nhiên, kiểm tra dữ liệu thực tế cần được thiết kế theo nghiệp vụ. Ví dụ, email cần kiểm tra định dạng email, số điện thoại cần kiểm tra quy tắc mà website yêu cầu và một trường số lượng cần được kiểm tra kiểu cũng như giới hạn giá trị.

Kiểm tra JSON lỗi bằng json_last_error()

Không phải chuỗi nào gửi tới server cũng là JSON hợp lệ. Nếu dữ liệu bị cắt giữa chừng, sai dấu ngoặc hoặc sai cú pháp, json_decode() có thể không tạo ra dữ liệu mong muốn.

Có thể kiểm tra lỗi theo cách truyền thống:

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

if (json_last_error() !== JSON_ERROR_NONE) {
    http_response_code(400);

    echo json_encode([
        "success" => false,
        "message" => "Không thể đọc dữ liệu JSON"
    ]);

    exit;
}

Với các phiên bản PHP hiện đại, có thể sử dụng JSON_THROW_ON_ERROR để xử lý lỗi rõ ràng hơn:

try {
    $data = json_decode(
        $rawData,
        true,
        512,
        JSON_THROW_ON_ERROR
    );
} catch (JsonException $e) {
    http_response_code(400);

    echo json_encode([
        "success" => false,
        "message" => "JSON không hợp lệ"
    ]);

    exit;
}

Cách tiếp cận này đặc biệt hữu ích trong những hệ thống có nhiều endpoint vì lỗi giải mã JSON được đưa vào một luồng xử lý rõ ràng thay vì phải kiểm tra thủ công ở nhiều vị trí.

Trả dữ liệu JSON từ PHP về JavaScript

Sau khi server xử lý xong request, PHP cần tạo response để JavaScript có thể đọc. Một response JSON hoàn chỉnh không chỉ cần nội dung đúng mà còn nên khai báo rõ kiểu dữ liệu thông qua HTTP header.

Ví dụ:

<?php

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

echo json_encode([
    "success" => true,
    "message" => "Xử lý thành công",
    "data" => [
        "id" => 125,
        "name" => "Website bán hàng"
    ]
]);

Dòng Content-Type cho client biết response chứa JSON. Việc khai báo charset UTF-8 cũng giúp dữ liệu tiếng Việt và các ký tự Unicode được xử lý nhất quán.

Phía JavaScript có thể nhận kết quả:

fetch("process.php", {
  method: "POST",
  headers: {
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    name: "Nguyen Van A"
  })
})
.then(response => response.json())
.then(data => {
  if (data.success) {
    console.log(data.message);
    console.log(data.data);
  } else {
    console.log(data.message);
  }
});

Như vậy, dữ liệu đi theo một vòng khép kín: JavaScript tạo object, chuyển thành JSON, PHP nhận chuỗi JSON, giải mã thành dữ liệu PHP, xử lý nghiệp vụ, sau đó dùng json_encode() để tạo JSON response và JavaScript phân tích response trở lại thành dữ liệu có thể sử dụng.

json_encode() không chỉ đơn giản là nối chuỗi

Khi tạo JSON bằng PHP, không nên tự nối chuỗi để tạo cấu trúc JSON. Cách này rất dễ làm hỏng dữ liệu khi chuỗi chứa dấu ngoặc kép, ký tự xuống dòng hoặc ký tự Unicode đặc biệt.

Thay vì viết thủ công:

$json = '{"name":"' . $name . '","price":' . $price . '}';

nên tạo array hoặc object PHP rồi để json_encode() đảm nhiệm việc chuyển đổi:

$response = [
    "name" => $name,
    "price" => $price
];

echo json_encode($response);

Cách này vừa dễ đọc vừa giảm nguy cơ tạo JSON sai cú pháp. Đặc biệt với dữ liệu đến từ người dùng, việc để thư viện xử lý quá trình encode an toàn và nhất quán hơn nhiều so với tự ghép chuỗi.

Phân biệt dữ liệu JSON, chuỗi JSON và object JavaScript

Trong quá trình lập trình, ba khái niệm này rất dễ bị dùng lẫn với nhau. Hiểu sự khác biệt sẽ giúp tìm lỗi AJAX nhanh hơn.

Khái niệm Đặc điểm Ví dụ
Object JavaScript Dữ liệu đang tồn tại dưới dạng object trong JavaScript { name: "Web Moi" }
Chuỗi JSON Dữ liệu JSON đang được biểu diễn dưới dạng text {"name":"Web Moi"}
Object PHP Dữ liệu đã được PHP giải mã thành cấu trúc PHP $data->name
Array PHP Object JSON được giải mã thành associative array $data["name"]

Ví dụ, JavaScript có object:

const data = {
  name: "Web Moi",
  price: 6000000
};

Khi gọi:

JSON.stringify(data);

giá trị trở thành một chuỗi JSON để truyền đi.

Ở chiều ngược lại, nếu nhận được chuỗi JSON từ server, JavaScript có thể dùng:

const data = JSON.parse(jsonString);

để chuyển chuỗi đó thành object JavaScript.

Trong thực tế, khi sử dụng response.json(), trình duyệt đã hỗ trợ bước phân tích response JSON nên không cần gọi JSON.parse() lần nữa. Gọi parse thêm một lần trong trường hợp này có thể gây lỗi vì dữ liệu lúc đó đã là object JavaScript.

Ứng dụng GET và POST khi trao đổi dữ liệu JSON

Trong một hệ thống AJAX JSON, GET và POST không chỉ khác nhau ở tên phương thức HTTP mà còn khác nhau về mục đích sử dụng. Chọn đúng phương thức ngay từ đầu giúp API dễ hiểu, dễ bảo trì và hạn chế những cách xử lý thiếu nhất quán.

GET thường được sử dụng khi trình duyệt muốn lấy dữ liệu từ server. Ví dụ: lấy danh sách sản phẩm, xem thông tin một bài viết hoặc tìm kiếm theo từ khóa.

POST thường phù hợp với những thao tác gửi dữ liệu lên server để tạo mới hoặc thực hiện một nghiệp vụ. Ví dụ: tạo đơn hàng, gửi biểu mẫu, đăng nhập hoặc thêm một sản phẩm.

Với GET, tham số thường được đặt trong URL:

fetch("products.php?category=website&page=2")
  .then(response => response.json())
  .then(data => {
    console.log(data);
  });

PHP có thể đọc các tham số này thông qua $_GET:

<?php

$category = $_GET["category"] ?? "";
$page = (int)($_GET["page"] ?? 1);

Trong khi đó, POST có thể gửi dữ liệu JSON trong request body:

fetch("products.php", {
  method: "POST",
  headers: {
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    category: "website",
    page: 2
  })
})
.then(response => response.json())
.then(data => {
  console.log(data);
});

PHP tiếp nhận body bằng php://input rồi giải mã JSON như đã trình bày ở phần trước.

Khi nào nên dùng GET?

GET phù hợp với những request chủ yếu có mục đích đọc dữ liệu. Ví dụ một ô tìm kiếm có thể gửi từ khóa lên server để nhận về danh sách kết quả.

Ưu điểm của cách này là URL có thể chứa các tham số rõ ràng. Điều đó thuận tiện cho việc kiểm tra request, chia sẻ URL hoặc sử dụng các cơ chế cache phù hợp.

Tuy nhiên, không nên đưa những dữ liệu nhạy cảm như mật khẩu, mã bí mật hoặc thông tin riêng tư vào query string. Dữ liệu trên URL có thể xuất hiện trong lịch sử trình duyệt, log máy chủ, hệ thống phân tích hoặc các thành phần trung gian.

Khi nào nên dùng POST?

POST phù hợp hơn khi request mang dữ liệu cần server xử lý. Ví dụ đăng ký tài khoản, gửi thông tin liên hệ, tạo đơn hàng hoặc cập nhật một nghiệp vụ.

POST không có nghĩa là dữ liệu tự động được bảo mật. Nếu website sử dụng HTTP thay vì HTTPS, dữ liệu truyền qua mạng vẫn có thể bị quan sát. Ngoài ra, việc dùng POST cũng không thay thế cho các cơ chế xác thực, phân quyền, chống CSRF hoặc kiểm tra dữ liệu đầu vào.

Lấy dữ liệu từ database và trả về danh sách JSON

Một ứng dụng thực tế thường không trả JSON từ những dữ liệu cố định. Server sẽ nhận request, truy vấn cơ sở dữ liệu rồi chuyển kết quả thành JSON.

Ví dụ với PHP và PDO, một endpoint có thể lấy danh sách sản phẩm theo danh mục:

<?php

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

require "database.php";

$category = trim($_GET["category"] ?? "");

$stmt = $pdo->prepare(
    "SELECT id, name, price
     FROM products
     WHERE category = :category
     ORDER BY id DESC"
);

$stmt->execute([
    "category" => $category
]);

$products = $stmt->fetchAll(PDO::FETCH_ASSOC);

echo json_encode([
    "success" => true,
    "data" => $products
]);

Điểm đáng chú ý ở đây là câu SQL sử dụng prepared statement thay vì nối trực tiếp giá trị từ URL vào câu lệnh SQL. Đây là nguyên tắc quan trọng để giảm nguy cơ SQL Injection.

Kết quả có thể có dạng:

{
  "success": true,
  "data": [
    {
      "id": 10,
      "name": "Website doanh nghiệp",
      "price": 8000000
    },
    {
      "id": 9,
      "name": "Website bán hàng",
      "price": 6000000
    }
  ]
}

JavaScript có thể duyệt qua data để hiển thị danh sách:

fetch("products.php?category=website")
  .then(response => response.json())
  .then(result => {
    if (!result.success) {
      console.error(result.message);
      return;
    }

    result.data.forEach(product => {
      console.log(product.name, product.price);
    });
  });

Trong hệ thống lớn, endpoint nên đảm nhận phần lấy và chuẩn hóa dữ liệu, còn việc quyết định hiển thị sản phẩm như thế nào nên để tầng giao diện xử lý. Nhờ vậy, cùng một nguồn JSON có thể được sử dụng cho nhiều thành phần khác nhau.

Xử lý response lỗi đúng cách

Một request AJAX không nên chỉ quan tâm trường hợp thành công. Trong thực tế, request có thể thất bại vì dữ liệu không hợp lệ, người dùng chưa đăng nhập, không đủ quyền, endpoint không tồn tại hoặc server gặp lỗi.

Ví dụ, nếu người dùng gửi thiếu email:

http_response_code(422);

echo json_encode([
    "success" => false,
    "message" => "Email không được để trống",
    "errors" => [
        "email" => "Vui lòng nhập email"
    ]
]);

JavaScript không nên mặc định rằng mọi response HTTP đều là thành công. Với fetch(), một HTTP 404 hoặc 500 không tự động khiến Promise rơi vào catch(). Vì vậy cần chủ động kiểm tra response.ok hoặc response.status.

fetch("process.php", {
  method: "POST",
  headers: {
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    email: "a@example.com"
  })
})
.then(async response => {
  const data = await response.json();

  if (!response.ok) {
    throw new Error(data.message || "Request thất bại");
  }

  return data;
})
.then(data => {
  console.log(data);
})
.catch(error => {
  console.error(error.message);
});

Cách này giúp phân biệt được hai nhóm lỗi: request có response từ server nhưng server báo lỗi nghiệp vụ, và những lỗi khiến quá trình giao tiếp hoặc xử lý response không thể hoàn thành.

Không nên trả HTML lẫn vào JSON

Một endpoint được thiết kế để trả JSON nên duy trì response ở dạng JSON. Đây là nguyên tắc rất quan trọng nhưng thường bị phá vỡ bởi những đoạn echo hoặc warning PHP xuất hiện ngoài JSON.

Ví dụ response đáng lẽ phải là:

{
  "success": true,
  "message": "Thành công"
}

nhưng server lại phát sinh warning trước JSON:

Warning: Undefined variable $name

{
  "success": true,
  "message": "Thành công"
}

Toàn bộ response lúc này không còn là JSON hợp lệ. Khi JavaScript gọi response.json(), quá trình phân tích có thể thất bại.

Do đó, endpoint API nên được tổ chức để không xuất thông báo lỗi PHP trực tiếp vào response production. Lỗi cần được ghi log ở phía server và response gửi cho client phải tuân thủ cấu trúc đã thiết kế.

Cập nhật giao diện sau khi nhận JSON

Mục tiêu lớn của AJAX không chỉ là lấy dữ liệu mà còn là giúp giao diện phản hồi mà không phải tải lại toàn bộ trang. Sau khi nhận JSON, JavaScript có thể thay đổi nội dung của một phần HTML.

Ví dụ server trả về danh sách sản phẩm, JavaScript có thể tạo nội dung:

fetch("products.php")
  .then(response => response.json())
  .then(result => {
    const list = document.querySelector("#product-list");

    if (!result.success) {
      list.textContent = result.message;
      return;
    }

    list.innerHTML = "";

    result.data.forEach(product => {
      const item = document.createElement("div");

      item.textContent =
        product.name + " - " + product.price;

      list.appendChild(item);
    });
  });

Trong ví dụ này, textContent được sử dụng khi đưa dữ liệu trực tiếp vào nội dung văn bản. Đây là lựa chọn an toàn hơn so với đưa dữ liệu chưa được kiểm soát vào innerHTML.

Nếu cần tạo HTML phức tạp bằng innerHTML, dữ liệu lấy từ người dùng hoặc nguồn không đáng tin cậy phải được xử lý phù hợp để tránh tạo lỗ hổng XSS.

AJAX không có nghĩa là mọi thứ phải render ở client

Không phải cứ dùng AJAX là server phải trả về toàn bộ dữ liệu thô để JavaScript tự xây dựng mọi HTML. Có những trường hợp server trả HTML fragment sẽ phù hợp hơn, đặc biệt với website PHP truyền thống.

Điểm quan trọng là phải xác định rõ trách nhiệm giữa server và client. Nếu hệ thống sử dụng JSON làm hợp đồng dữ liệu, client nên nhận dữ liệu có cấu trúc và quyết định cách hiển thị. Nếu server được thiết kế theo mô hình trả về HTML fragment, việc trả HTML có thể hợp lý hơn.

Không nên kết hợp hai cách một cách tùy tiện, chẳng hạn response vừa chứa một phần HTML vừa chứa JSON nhưng không có quy ước rõ ràng. Điều đó khiến JavaScript khó xử lý và làm API khó tái sử dụng.

Những lỗi AJAX JSON thường gặp khi lập trình website

Phần lớn lỗi không nằm ở bản thân AJAX mà xuất phát từ việc client và server không thống nhất cách gửi, nhận hoặc xử lý dữ liệu.

Lỗi Nguyên nhân thường gặp Cách kiểm tra
PHP không nhận được dữ liệu Gửi JSON nhưng lại đọc bằng $_POST Kiểm tra Content-Type và php://input
response.json() bị lỗi Server trả HTML, warning hoặc dữ liệu không phải JSON Kiểm tra Response trong DevTools
Dữ liệu bị undefined Client truy cập sai tên key Kiểm tra chính xác cấu trúc JSON
Request bị 404 Sai URL endpoint hoặc đường dẫn tương đối Kiểm tra Request URL
Request bị 500 Lỗi phía PHP hoặc server Kiểm tra log server
Request bị chặn CORS hoặc chính sách bảo mật trình duyệt Kiểm tra Console và response header

Khi gặp lỗi, thay vì sửa JavaScript một cách ngẫu nhiên, nên mở Developer Tools của trình duyệt và kiểm tra tab Network. Đây thường là nơi nhanh nhất để biết request đã được gửi chưa, URL có đúng không, method là gì, request body chứa gì, HTTP status bao nhiêu và server thực sự trả về nội dung nào.

Nếu request đã gửi đúng nhưng response sai, cần chuyển sang kiểm tra PHP. Nếu PHP xử lý đúng nhưng trình duyệt không đọc được response, hãy kiểm tra cấu trúc JSON và header. Phân tích theo từng tầng như vậy sẽ nhanh hơn rất nhiều so với việc thay đổi code ở cả client lẫn server cùng lúc.

Viết AJAX JSON bằng async và await

Promise với .then() hoàn toàn có thể sử dụng để xử lý AJAX. Tuy nhiên, khi một chức năng có nhiều bước liên tiếp như gửi dữ liệu, kiểm tra HTTP status, đọc JSON, kiểm tra kết quả nghiệp vụ rồi cập nhật giao diện, chuỗi .then() dài có thể trở nên khó theo dõi.

asyncawait giúp viết cùng một quy trình theo cách gần với trình tự xử lý thông thường hơn. Điều này đặc biệt hữu ích khi website có nhiều request hoặc một thao tác phụ thuộc vào kết quả của request trước đó.

async function loadProducts() {
  try {
    const response = await fetch("products.php");

    if (!response.ok) {
      throw new Error("Không thể tải dữ liệu");
    }

    const result = await response.json();

    if (!result.success) {
      throw new Error(result.message);
    }

    console.log(result.data);
  } catch (error) {
    console.error(error.message);
  }
}

loadProducts();

Ưu điểm lớn của cách viết này là toàn bộ luồng xử lý nằm trong một khối dễ đọc. Tuy nhiên, await không làm request trở thành đồng bộ theo nghĩa trình duyệt bị khóa. Nó chỉ khiến hàm async tạm dừng tại vị trí đó cho đến khi Promise hoàn thành, trong khi luồng giao diện của trình duyệt vẫn có thể tiếp tục hoạt động.

Xử lý trạng thái loading

Một giao diện AJAX tốt cần cho người dùng biết hệ thống đang xử lý. Nếu request mất vài giây nhưng không có phản hồi trực quan, người dùng có thể bấm nút nhiều lần hoặc nghĩ website bị treo.

async function submitForm() {
  const button = document.querySelector("#submit-button");

  button.disabled = true;

  try {
    const response = await fetch("process.php", {
      method: "POST",
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify({
        name: "Nguyen Van A"
      })
    });

    const result = await response.json();

    if (!response.ok) {
      throw new Error(result.message || "Có lỗi xảy ra");
    }

    console.log(result.message);
  } catch (error) {
    console.error(error.message);
  } finally {
    button.disabled = false;
  }
}

Khối finally đặc biệt hữu ích cho những thao tác cần khôi phục giao diện dù request thành công hay thất bại. Trong ví dụ trên, nút được bật lại sau khi quá trình xử lý kết thúc.

Bảo mật khi nhận và xử lý dữ liệu JSON

JSON chỉ là định dạng dữ liệu. Việc sử dụng JSON không khiến request tự động an toàn. Dữ liệu được gửi từ trình duyệt vẫn có thể bị người dùng thay đổi trước khi tới server. Vì vậy, mọi dữ liệu từ client phải được xem là dữ liệu không đáng tin cậy.

Server cần tự kiểm tra dữ liệu theo nghiệp vụ thay vì tin vào những thông tin do JavaScript gửi lên. Nếu client gửi:

{
  "product_id": 15,
  "quantity": 2,
  "price": 500000
}

không nên mặc định sử dụng giá 500000 từ request để tạo đơn hàng. Giá sản phẩm nên được lấy lại từ dữ liệu đáng tin cậy ở phía server. Nếu không, người dùng có thể sửa request thành một mức giá khác.

Tương tự, những thông tin như quyền quản trị, trạng thái thanh toán hoặc trạng thái đơn hàng không nên được xác định dựa trên một trường JSON do client tự gửi.

Không dùng JSON để thay thế xác thực và phân quyền

Một endpoint có thể nhận JSON hoàn toàn chính xác nhưng vẫn là một endpoint không an toàn nếu bất kỳ ai cũng có thể gọi và thực hiện nghiệp vụ nhạy cảm.

Server cần xác định người gửi request là ai và người đó có quyền thực hiện thao tác hay không. Hai vấn đề này thuộc về authenticationauthorization, không phải chức năng của JSON.

Ví dụ, việc JavaScript gửi:

{
  "action": "delete",
  "id": 125
}

không có nghĩa server phải thực hiện xóa. Server cần kiểm tra phiên đăng nhập, quyền của tài khoản và điều kiện nghiệp vụ trước khi cho phép thao tác.

Chống XSS khi đưa dữ liệu JSON lên giao diện

Dữ liệu JSON có thể chứa nội dung do người dùng nhập. Nếu JavaScript lấy dữ liệu đó rồi chèn trực tiếp vào HTML bằng innerHTML, website có thể tạo ra nguy cơ Cross-Site Scripting.

Ví dụ, thay vì đưa dữ liệu chưa kiểm soát trực tiếp vào HTML, có thể tạo phần tử và sử dụng textContent:

const title = document.createElement("div");
title.textContent = product.name;

container.appendChild(title);

Nếu bắt buộc phải tạo HTML động, cần có cơ chế escape hoặc sanitization phù hợp với loại dữ liệu. Không nên coi JSON là nguồn dữ liệu an toàn chỉ vì nó có cấu trúc rõ ràng.

CORS và vấn đề khác domain khi sử dụng AJAX

Một trường hợp thường gặp là JavaScript chạy ở một domain nhưng endpoint JSON nằm trên domain khác. Khi đó, trình duyệt có thể áp dụng chính sách Same-Origin Policy và yêu cầu server đích cho phép truy cập thông qua CORS.

Ví dụ, website:

https://webmoi.vn

gọi API:

https://api.example.com/products

thì đây là request khác origin. Server API cần cấu hình CORS phù hợp nếu muốn trình duyệt cho phép website đầu tiên đọc response.

Trong PHP, một cấu hình đơn giản có thể trông như:

<?php

header("Access-Control-Allow-Origin: https://webmoi.vn");
header("Content-Type: application/json; charset=utf-8");

Không nên tùy tiện sử dụng * cho mọi API, đặc biệt với những endpoint có dữ liệu riêng tư hoặc sử dụng cookie xác thực. CORS là cơ chế kiểm soát truy cập của trình duyệt, không phải cơ chế thay thế authentication.

Preflight OPTIONS là gì?

Một số request cross-origin có thể khiến trình duyệt gửi request OPTIONS trước request chính. Đây được gọi là preflight. Mục đích là để trình duyệt kiểm tra server có cho phép phương thức, header hoặc nguồn gửi request hay không.

Nếu API chỉ xử lý POST mà không xử lý đúng OPTIONS trong trường hợp cần preflight, request chính có thể không được thực hiện dù endpoint POST hoàn toàn tồn tại.

Do đó, khi gặp lỗi CORS, không nên chỉ kiểm tra request POST. Hãy mở Network trong Developer Tools và xem trình duyệt có gửi OPTIONS hay không, response của OPTIONS là gì và server có trả các header CORS cần thiết hay chưa.

Tối ưu hiệu năng khi sử dụng JSON

JSON khá nhẹ và tiện lợi nhưng không có nghĩa response càng lớn càng tốt. Một API trả hàng chục nghìn bản ghi chỉ để giao diện hiển thị vài chục dòng sẽ gây lãng phí băng thông, bộ nhớ và thời gian xử lý.

Với danh sách lớn, nên áp dụng phân trang:

{
  "success": true,
  "data": [
    {
      "id": 101,
      "name": "Sản phẩm A"
    }
  ],
  "pagination": {
    "page": 1,
    "per_page": 20,
    "total": 250
  }
}

Thay vì tải toàn bộ 250 sản phẩm ngay từ đầu, client chỉ yêu cầu số lượng cần thiết. Khi người dùng chuyển trang hoặc tải thêm, AJAX tiếp tục lấy phần dữ liệu tiếp theo.

Đối với tìm kiếm, nên cân nhắc debounce để tránh gửi request sau từng ký tự mà người dùng nhập.

let timer;

searchInput.addEventListener("input", function () {
  clearTimeout(timer);

  timer = setTimeout(() => {
    searchProducts(this.value);
  }, 300);
});

Cách này đặc biệt hữu ích với ô tìm kiếm trực tiếp. Người dùng có thể nhập liên tục nhưng server chỉ nhận request sau khi họ tạm dừng trong khoảng thời gian đã định.

Ngoài ra, API nên chỉ trả những trường thực sự cần thiết. Nếu giao diện chỉ hiển thị tên và giá sản phẩm, không nhất thiết phải trả thêm hàng chục trường dữ liệu khác. Response nhỏ hơn giúp giảm lượng dữ liệu truyền tải và giảm công việc xử lý ở client.

Thiết kế endpoint JSON dễ bảo trì

Một endpoint tốt không chỉ chạy được mà còn phải dễ hiểu đối với người phát triển tiếp theo. Ngay từ đầu nên thống nhất cách đặt URL, HTTP method, cấu trúc request và cấu trúc response.

Ví dụ, thay vì tạo hàng loạt endpoint với cách đặt tên tùy ý, có thể tổ chức theo tài nguyên:

GET    /api/products
GET    /api/products/125
POST   /api/products
PUT    /api/products/125
DELETE /api/products/125

Đây là một cách tổ chức theo hướng RESTful. Không phải dự án nào cũng cần tuân thủ REST tuyệt đối, nhưng tư duy thiết kế tài nguyên và phương thức rõ ràng sẽ giúp API dễ sử dụng hơn.

Cấu trúc response cũng nên có quy ước thống nhất. Chẳng hạn, tất cả response có thể sử dụng các trường như success, messagedata. Những lỗi validation có thể nằm trong errors.

Khi API được sử dụng bởi nhiều màn hình hoặc nhiều ứng dụng khác nhau, việc duy trì hợp đồng dữ liệu ổn định càng quan trọng. Nếu thay đổi tên key hoặc kiểu dữ liệu mà không có kế hoạch, những client đang sử dụng API có thể ngừng hoạt động.

Không nên nhồi quá nhiều nghiệp vụ vào JavaScript

JavaScript có thể kiểm tra dữ liệu để tạo trải nghiệm tốt cho người dùng, nhưng các kiểm tra quan trọng vẫn phải được thực hiện lại ở server.

Ví dụ, JavaScript có thể kiểm tra email có vẻ hợp lệ trước khi gửi để người dùng nhận phản hồi nhanh. Tuy nhiên, PHP vẫn phải kiểm tra email khi request tới server. Client-side validation phục vụ trải nghiệm; server-side validation bảo vệ nghiệp vụ.

Tương tự, việc ẩn một nút trên giao diện không đồng nghĩa với việc người dùng không thể gọi trực tiếp endpoint. Nếu một tài khoản không có quyền thực hiện thao tác, server phải từ chối request dù giao diện có hiển thị nút đó hay không.

Quy trình debug AJAX JSON hiệu quả

Khi một chức năng AJAX không hoạt động, cách nhanh nhất không phải là sửa code theo phỏng đoán mà là xác định request đang hỏng ở tầng nào.

  1. Kiểm tra Console xem JavaScript có lỗi cú pháp hoặc runtime hay không.
  2. Mở Network và xác định request AJAX có thực sự được gửi hay không.
  3. Kiểm tra Request URL có đúng endpoint hay không.
  4. Kiểm tra HTTP method là GET, POST hay phương thức khác.
  5. Kiểm tra Request Headers, đặc biệt là Content-Type khi gửi JSON.
  6. Kiểm tra Request Payload để xác nhận dữ liệu thực tế được gửi đi.
  7. Kiểm tra HTTP status code.
  8. Kiểm tra Response thực tế server trả về.
  9. Nếu response không đúng, kiểm tra PHP và log server.
  10. Nếu response đúng nhưng giao diện không thay đổi, kiểm tra logic JavaScript sau khi parse JSON.

Ví dụ, nếu response.json() báo lỗi, chưa nên kết luận rằng JSON phía JavaScript có vấn đề. Hãy mở phần Response trong Network và xem nội dung thực tế. Nếu response bắt đầu bằng thông báo PHP, HTML của trang lỗi hoặc một warning, nguyên nhân nằm ở server chứ không phải ở response.json().

Ngược lại, nếu response là JSON hợp lệ nhưng JavaScript báo undefined, hãy so sánh chính xác tên key. Ví dụ server trả data.products nhưng client lại đọc result.items thì JSON vẫn hoàn toàn hợp lệ, chỉ là hai bên không thống nhất cấu trúc dữ liệu.

Khi nào AJAX JSON là lựa chọn phù hợp?

AJAX JSON đặc biệt phù hợp với những website có nhiều thao tác cần phản hồi nhanh mà không muốn tải lại toàn bộ trang. Có thể kể đến tìm kiếm trực tiếp, lọc sản phẩm, phân trang, gửi biểu mẫu, kiểm tra dữ liệu, thêm giỏ hàng, cập nhật trạng thái hoặc tải thêm nội dung.

Với website PHP truyền thống, không nhất thiết phải chuyển toàn bộ hệ thống sang một framework JavaScript lớn mới có thể sử dụng mô hình này. PHP có thể tiếp tục đảm nhiệm việc xử lý nghiệp vụ và dữ liệu, trong khi JavaScript sử dụng fetch() để giao tiếp với những endpoint cần thiết.

Điều quan trọng hơn công nghệ là cách phân chia trách nhiệm. Server chịu trách nhiệm xác thực, phân quyền, nghiệp vụ và dữ liệu đáng tin cậy. JavaScript chịu trách nhiệm tương tác và cập nhật giao diện. JSON đóng vai trò là cầu nối dữ liệu có cấu trúc giữa hai phía.

Đối với một website được xây dựng theo yêu cầu, cách triển khai này có thể giúp tránh việc tải lại những thành phần không thay đổi, đồng thời vẫn giữ kiến trúc tương đối đơn giản nếu nhu cầu của dự án chưa cần đến một ứng dụng frontend phức tạp.

Những nguyên tắc nên nhớ khi làm việc với AJAX JSON

  • AJAX là cơ chế giao tiếp bất đồng bộ; JSON là định dạng dữ liệu, hai khái niệm không đồng nhất.
  • JSON gửi từ JavaScript nên được tạo bằng JSON.stringify() thay vì tự nối chuỗi.
  • PHP nhận JSON request body thông qua php://input, không nên mặc định đọc bằng $_POST.
  • Dùng json_decode() để chuyển JSON thành dữ liệu PHP và json_encode() để tạo response JSON.
  • Response JSON nên khai báo Content-Type: application/json.
  • Không coi dữ liệu từ client là đáng tin cậy, dù dữ liệu đã được gửi dưới dạng JSON.
  • Không dựa vào JavaScript để bảo vệ những quy tắc nghiệp vụ quan trọng.
  • Kiểm tra HTTP status cùng nội dung JSON thay vì chỉ kiểm tra một trường success.
  • Khi hiển thị dữ liệu do người dùng cung cấp, cần chú ý đến nguy cơ XSS.
  • Request cross-origin cần được xem xét về CORS và preflight.
  • Danh sách lớn nên có phân trang, lọc và giới hạn dữ liệu trả về.
  • Khi debug, Network của Developer Tools thường là nơi quan trọng nhất để xác định request và response thực tế.

AJAX JSON thực chất không khó nếu nhìn nó như một chuỗi giao tiếp rõ ràng: giao diện tạo request → server tiếp nhận → server kiểm tra và xử lý → server trả JSON → JavaScript đọc kết quả → giao diện cập nhật. Phần khó nằm ở việc thiết kế dữ liệu, xử lý lỗi, bảo mật và thống nhất cách giao tiếp giữa hai phía.

Khi những nguyên tắc này được xây dựng ngay từ đầu, AJAX không chỉ giúp website phản hồi nhanh hơn mà còn tạo ra một cấu trúc giao tiếp rõ ràng giữa frontend và backend. Đây là nền tảng quan trọng để Web Mới triển khai những website PHP theo yêu cầu có giao diện linh hoạt nhưng vẫn giữ được khả năng kiểm soát, bảo trì và mở rộng về sau.

  • 0 Bình luận
CEO Bùi Tấn Lực | Founder Web Mới
Bùi Tấn Lực
Tìm hiểu về CEO Bùi Tấn Lực, Founder Web Mới với nhiều năm kinh nghiệm trong lĩnh vực phát triển website, SEO và chia sẻ kiến thức công nghệ
Đánh giá
Chia sẻ nội dung đánh giá của bạn về AJAX JSON là gì? Cách trao đổi và xử lý dữ liệu JSON
Email, Điện thoại của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *
Đánh giá của bạn
Tên *
Email
Số điện thoại *
Bình luận, Hỏi đáp
Yêu Cầu Báo Giá
Gửi trang web mẫu cần làm theo, chúng tôi sẽ báo giá đến bạn từ Email (tanlucit09@gmail.com - Bùi Tấn Lực) hoặc Zalo (Lực IT - 0398259259) !