AJAX
Web Development
Programming
JavaScript
Backend Development

How can I make an AJAX call without jQuery?

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

You do not need jQuery to make AJAX requests in modern browsers. Native browser APIs already cover the common cases, and the best default today is fetch, with XMLHttpRequest kept around mainly for older codebases or specific compatibility requirements.

Use fetch for New Code

The fetch API is promise-based and works naturally with async and await. A simple GET request looks like this:

javascript
1async function loadUsers() {
2  const response = await fetch("/api/users");
3
4  if (!response.ok) {
5    throw new Error(`Request failed with status ${response.status}`);
6  }
7
8  const users = await response.json();
9  console.log(users);
10}
11
12loadUsers().catch((error) => {
13  console.error(error.message);
14});

This is the modern replacement for a basic $.ajax call. The request is asynchronous, and the control flow is much easier to follow than old callback-heavy patterns.

Send JSON or Other Payloads Explicitly

For POST, PUT, or PATCH requests, specify the method, headers, and body clearly.

javascript
1async function createUser() {
2  const response = await fetch("/api/users", {
3    method: "POST",
4    headers: {
5      "Content-Type": "application/json"
6    },
7    body: JSON.stringify({
8      name: "Ava",
9      role: "admin"
10    })
11  });
12
13  if (!response.ok) {
14    throw new Error(`Server returned ${response.status}`);
15  }
16
17  const saved = await response.json();
18  console.log(saved);
19}
20
21createUser().catch(console.error);

If the backend expects form data or URL-encoded payloads, send that format instead of assuming JSON is always correct.

Handle Errors Correctly

A common mistake is thinking fetch rejects the promise for HTTP errors such as 404 or 500. It does not. It only rejects for network-level failures, request abortion, or similar lower-level problems. You still need to inspect response.ok.

javascript
1async function loadProfile() {
2  try {
3    const response = await fetch("/api/profile");
4
5    if (!response.ok) {
6      const text = await response.text();
7      throw new Error(`Profile request failed: ${text}`);
8    }
9
10    return await response.json();
11  } catch (error) {
12    console.error("Unable to load profile", error);
13    return null;
14  }
15}

This keeps HTTP status handling explicit instead of assuming the runtime will treat all failure modes the same way.

XMLHttpRequest Still Exists

If you maintain older code, XMLHttpRequest is still a valid browser API. It is more verbose, but it works without jQuery too.

javascript
1const xhr = new XMLHttpRequest();
2xhr.open("GET", "/api/users");
3xhr.onreadystatechange = function () {
4  if (xhr.readyState === 4) {
5    if (xhr.status >= 200 && xhr.status < 300) {
6      console.log(JSON.parse(xhr.responseText));
7    } else {
8      console.error(`Request failed with status ${xhr.status}`);
9    }
10  }
11};
12xhr.send();

The main reason to prefer fetch in new code is readability and simpler composition with async JavaScript.

Connect the Request to the UI

In real applications, a request is only part of the job. The UI should show loading state, success, and failure clearly.

javascript
1const button = document.querySelector("#load-users");
2const output = document.querySelector("#result");
3
4button.addEventListener("click", async () => {
5  output.textContent = "Loading...";
6
7  try {
8    const response = await fetch("/api/users");
9    if (!response.ok) {
10      throw new Error("Unable to load users");
11    }
12
13    const users = await response.json();
14    output.textContent = users.map((user) => user.name).join(", ");
15  } catch (error) {
16    output.textContent = error.message;
17  }
18});

That turns the request into a real interaction instead of just a console demo.

Common Pitfalls

  • Expecting fetch to throw automatically for 404 or 500 responses leads to incomplete error handling.
  • Sending JSON when the server expects a different content type causes avoidable API bugs.
  • Updating the UI before checking response.ok and parsing the response safely makes failures confusing.
  • Keeping jQuery only for AJAX in a modern browser-only app adds dependency weight without much value.
  • Ignoring cancellation or repeated requests can create race conditions in interactive screens.

Summary

  • Use fetch as the default way to make AJAX requests without jQuery.
  • Specify method, headers, and body explicitly for non-GET requests.
  • Check response.ok because HTTP errors do not reject fetch automatically.
  • Use XMLHttpRequest only for legacy or compatibility-driven code.
  • Connect request state to the UI so loading and failure are visible to the user.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.