← Back to Tutorials Chapter 11

AJAX & JSON

AJAX and JSON together form the backbone of modern web communication. This chapter dives deep into making HTTP requests from the browser, handling responses, and working with the JSON data format.

XMLHttpRequest (The Old Way)

XMLHttpRequest is the original browser API for making HTTP requests. It is event-driven and supports both synchronous and asynchronous modes.

const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://jsonplaceholder.typicode.com/posts/1');
xhr.onload = function() {
  if (xhr.status >= 200 && xhr.status < 300) {
    const data = JSON.parse(xhr.responseText);
    console.log('Title:', data.title);
  } else {
    console.error('Request failed:', xhr.status);
  }
};
xhr.onerror = () => console.error('Network error');
xhr.send();
Caveats: XMLHttpRequest does not support promises natively, making it verbose. It also ignores the Content-Type when using overrideMimeType(). Modern code should prefer the Fetch API.

Fetch API (Modern)

The Fetch API provides a cleaner, promise-based interface for making HTTP requests. It is built into all modern browsers.

GET Request

fetch('https://jsonplaceholder.typicode.com/posts')
  .then(response => {
    if (!response.ok) throw new Error(`HTTP ${response.status}`);
    return response.json();
  })
  .then(posts => console.log('Posts:', posts))
  .catch(err => console.error('Fetch error:', err));

POST Request

fetch('https://jsonplaceholder.typicode.com/posts', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    title: 'New Post',
    body: 'This is the content.',
    userId: 1
  })
})
  .then(res => res.json())
  .then(data => console.log('Created:', data))
  .catch(err => console.error(err));
AJAX request-response cycle

Handling Responses

The Response object provides several methods to read the body in different formats:

fetch('/data')
  .then(res => {
    console.log('Status:', res.status);
    console.log('Headers:', res.headers.get('Content-Type'));
    return res.text(); // or .json(), .blob(), .formData()
  })
  .then(data => console.log(data));

Error Handling Strategies

Network failures and non-2xx status codes must be handled explicitly.

async function fetchData(url) {
  try {
    const res = await fetch(url);
    if (!res.ok) throw new Error(`Status ${res.status}`);
    return await res.json();
  } catch (err) {
    console.error('Failed to fetch:', err.message);
    return null;
  }
}

JSON Structure & Types

JSON supports these data types: string, number, boolean, null, object, and array. All keys and string values must be enclosed in double quotes.

{
  "string": "Hello",
  "number": 42,
  "boolean": true,
  "nullValue": null,
  "array": [1, 2, 3],
  "object": { "nested": "value" }
}

JSON vs XML

JSON is more concise, easier to parse, and maps directly to JavaScript objects. XML is more verbose but supports attributes, namespaces, and schema validation.

<!-- XML version of the same data -->
<person>
  <name>Alice</name>
  <age>30</age>
</person>

// JSON equivalent
{"name": "Alice", "age": 30}
Tip: Use JSON.stringify(value, replacer, space) to pretty-print JSON with indentation. The space parameter controls the indentation width.

Working with REST APIs

REST APIs expose resources via standard HTTP methods. A typical workflow involves fetching a list, creating a new resource, updating one, and deleting it.

const BASE = 'https://jsonplaceholder.typicode.com';

async function apiDemo() {
  // GET all
  const todos = await fetch(`${BASE}/todos?_limit=3`).then(r => r.json());
  console.log('Todos:', todos);

  // POST create
  const created = await fetch(`${BASE}/todos`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ title: 'Learn Fetch', completed: false })
  }).then(r => r.json());
  console.log('Created:', created);
}

Async/Await with Fetch

Async/await makes asynchronous fetch code read like synchronous code, improving readability.

async function getUser(id) {
  const res = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);
  if (!res.ok) throw new Error('User not found');
  return await res.json();
}

(async () => {
  try {
    const user = await getUser(1);
    console.log(user.name);
  } catch (err) {
    console.error(err);
  }
})();

Exercise: GitHub User Search

Create a page with an input field and a button. When the user types a GitHub username and clicks the button, use the Fetch API to call https://api.github.com/users/{username}. Display the user’s avatar, name, bio, and public repository count. Handle errors gracefully if the user is not found.