What Is AJAX?
AJAX is not a language or library β it is a technique combining:
- JavaScript β orchestrates the request and handles the response
- XMLHttpRequest or Fetch API β sends the HTTP request
- JSON (or XML) β the data format exchanged (JSON dominates today)
- DOM manipulation β updates the page with the response data
Classic use cases include live search suggestions, infinite scroll, form submission without reload, and real-time dashboards.
XMLHttpRequest (XHR)
XHR was the only AJAX mechanism for over a decade. Understanding it explains why Fetch was created:
// XHR GET request
const xhr = new XMLHttpRequest();
// 1. Configure: method, URL, async (always true in modern code)
xhr.open('GET', 'https://jsonplaceholder.typicode.com/posts/1', true);
// 2. Set response type
xhr.responseType = 'json'; // auto-parse JSON
// 3. Attach event handlers
xhr.onreadystatechange = function() {
// readyState: 0=UNSENT, 1=OPENED, 2=HEADERS_RECEIVED, 3=LOADING, 4=DONE
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status >= 200 && xhr.status < 300) {
console.log('Response:', xhr.response);
} else {
console.error('Error:', xhr.status, xhr.statusText);
}
}
};
xhr.onerror = () => console.error('Network error');
// 4. Send the request
xhr.send();
Response: { userId: 1, id: 1, title: "sunt aut facere repellat...", body: "..." }
XHR POST Request
function xhrPost(url, data, callback) {
const xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.responseType = 'json';
xhr.onload = function() {
if (xhr.status >= 200 && xhr.status < 300) {
callback(null, xhr.response);
} else {
callback(new Error(`HTTP ${xhr.status}`), null);
}
};
xhr.onerror = () => callback(new Error('Network error'), null);
xhr.send(JSON.stringify(data));
}
// Usage
xhrPost(
'https://jsonplaceholder.typicode.com/posts',
{ title: 'AJAX Post', body: 'Posted via XHR', userId: 1 },
(err, data) => {
if (err) { console.error(err); return; }
console.log('Created:', data);
}
);
readyState and Status Codes
| Value | Constant | Meaning |
|---|---|---|
| readyState | ||
| 0 | UNSENT | open() not called yet |
| 1 | OPENED | open() called |
| 2 | HEADERS_RECEIVED | send() called; headers received |
| 3 | LOADING | Response body downloading |
| 4 | DONE | Operation complete |
| Common HTTP Status Codes | ||
| 200 | OK | Success |
| 201 | Created | Resource created (POST) |
| 204 | No Content | Success, no body (DELETE) |
| 400 | Bad Request | Invalid request data |
| 401 | Unauthorized | Authentication required |
| 404 | Not Found | Resource does not exist |
| 500 | Internal Server Error | Server-side bug |
Modern AJAX with Fetch
Fetch achieves the same goals with far less boilerplate:
// Live search using Fetch
const searchInput = document.getElementById('search');
const resultsList = document.getElementById('results');
let debounceTimer;
searchInput.addEventListener('input', () => {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(async () => {
const query = searchInput.value.trim();
if (!query) { resultsList.innerHTML = ''; return; }
try {
resultsList.innerHTML = '<li>Loading...</li>';
const res = await fetch(`/api/search?q=${encodeURIComponent(query)}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
resultsList.innerHTML = data.length
? data.map(item => `<li>${item.name}</li>`).join('')
: '<li>No results found</li>';
} catch (err) {
resultsList.innerHTML = `<li class="error">Error: ${err.message}</li>`;
}
}, 300); // 300 ms debounce
});
Axios β A Popular AJAX Library
Axios is a third-party library that wraps XHR/Fetch with extra features: automatic JSON parsing, interceptors, request cancellation, and better error handling out of the box:
// Include via CDN: <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
// Or install: npm install axios
// GET β Axios auto-parses JSON
async function getUsers() {
try {
const { data } = await axios.get('https://jsonplaceholder.typicode.com/users');
console.log(data); // already parsed array
} catch (error) {
// Axios DOES reject on HTTP errors (unlike native fetch)
console.error(error.response.status, error.message);
}
}
// POST
async function createUser(userData) {
const { data } = await axios.post('/api/users', userData, {
headers: { Authorization: 'Bearer my-token' }
});
return data;
}
// Axios instance with base URL and default headers
const api = axios.create({
baseURL: 'https://api.example.com',
timeout: 10000,
headers: { 'X-API-Key': 'abc123' }
});
// Use the instance
const { data: profile } = await api.get('/profile');
Use the native Fetch API for new projects to avoid extra dependencies β it handles 95% of use cases. Choose Axios when you need: request/response interceptors, progress events for file uploads, automatic HTTP error rejection, or when supporting environments where fetch isn't available natively.
CORS Policy Explained
Browsers enforce the Same-Origin Policy: a page at https://myapp.com cannot freely request https://api.other.com unless that server explicitly allows it via CORS headers.
// This will be BLOCKED by the browser if api.other.com doesn't allow it
fetch('https://api.other.com/data')
.catch(err => console.error(err)); // TypeError: Failed to fetch
// The server must respond with:
// Access-Control-Allow-Origin: https://myapp.com
// (or * for any origin)
// Preflight: for POST/PUT with custom headers, browser sends an OPTIONS
// request first to check permissions β Fetch handles this automatically.
// Workarounds (when you can't control the server):
// 1. A proxy server on YOUR domain that forwards requests
// 2. JSONP (legacy, only for GET, avoid in new code)
// Example: proxy pattern
const PROXY = 'https://myapp.com/proxy?url=';
fetch(PROXY + encodeURIComponent('https://api.other.com/data'))
.then(r => r.json())
.then(data => console.log(data));
ποΈ Practical Exercise
Build an infinite-scroll post list:
- On page load, fetch the first 10 posts from
https://jsonplaceholder.typicode.com/posts?_start=0&_limit=10. - Render each post as a card.
- When the user scrolls near the bottom of the page (use a
scrolllistener onwindowand comparescrollY + innerHeighttodocument.body.scrollHeight), fetch the next 10 posts. - Show a loading spinner while fetching; stop when all 100 posts are loaded.
π₯ Challenge Exercise
Build a GitHub user lookup tool using the public GitHub API (https://api.github.com/users/{username}). Features: text input + search button, display avatar, name, bio, followers, public repos; below that, load the user's top 5 repos by stars from /users/{username}/repos?sort=stars&per_page=5. Handle 404 (user not found) and rate limit (403) errors with appropriate messages. Use XHR for the user lookup and Fetch for the repos β deliberately mixing both for practice.
Summary
π Summary
- AJAX is a technique for loading data asynchronously without page reloads.
- XHR uses
open(),send(), andonreadystatechange; checkreadyState === 4andstatus. - Modern AJAX uses the Fetch API with Promises and
async/await. - Axios provides a cleaner API, auto JSON parsing, and HTTP error rejection.
- CORS is enforced by the browser; the server must send
Access-Control-Allow-Originheaders. - Debounce AJAX calls in search inputs to avoid excessive requests.
- JSONP is a legacy workaround β avoid in new code; use a proxy instead.
Interview Questions
- What does AJAX stand for and what problem does it solve?
- What are the readyState values in XHR and what does each mean?
- How does Axios differ from the native Fetch API?
- What is a CORS preflight request?
- How would you debounce an AJAX search request?
Frequently Asked Questions
Absolutely. React, Vue, and Angular all need to fetch data from APIs β they just abstract the Fetch or Axios calls into hooks or services. Understanding AJAX fundamentals makes you far more effective when debugging network issues, writing custom data-fetching logic, or working without a framework.
JSONP (JSON with Padding) is a legacy hack that injects a <script> tag pointing to a cross-origin URL. The server wraps the JSON in a callback function call, bypassing CORS. It only works for GET requests, is vulnerable to XSS if the server is compromised, and is completely superseded by CORS and proxy patterns. Don't use it in new code.
The Fetch API does not natively expose upload progress events. For upload progress, use XMLHttpRequest with xhr.upload.addEventListener('progress', handler), or use the Axios library which wraps XHR and exposes an onUploadProgress callback. The Streams API (via fetch request body as a ReadableStream) can provide some progress info but is experimental in most environments.