Fetching API Data in JavaScript: Loading, Errors and Responses

Fetching API data takes more than sending a request. Your interface also needs to handle waiting, empty results, failed requests and unexpected response formats.

This guide uses the browser’s Fetch API to explain those steps.

Fetch, Axios and async/await: what is the difference?

  • Fetch: A built-in web API for making HTTP requests.
  • Axios: An HTTP client library you can add to a project.
  • async/await: JavaScript syntax for working with promises, including those returned by Fetch or Axios.

They are not three competing request methods. The example below combines Fetch with async/await.

Check the response before using it

Fetch does not automatically reject a request because the server returns an HTTP error such as 404 or 500. Check response.ok before processing the response.

This illustrative function expects your application to provide a /api/articles endpoint returning an array of objects with string titles:

async function fetchArticles() {
  const response = await fetch("/api/articles");

  if (!response.ok) {
    throw new Error(`Request failed: ${response.status}`);
  }

  const articles = await response.json();

  if (
    !Array.isArray(articles) ||
    !articles.every(article =>
      article !== null &&
      typeof article === "object" &&
      typeof article.title === "string"
    )
  ) {
    throw new Error("Unexpected article data");
  }

  return articles;
}

Parsing JSON does not validate its structure. These checks confirm the fields needed for this example. A larger application may need more complete schema validation.

Represent the loading, empty and error states

For a small interface, start with a button, status message and results list:

<button id="load-articles" type="button">
  Load articles
</button>
<p id="article-status" role="status"></p>
<ul id="article-results"></ul>

Place the following JavaScript after those elements, alongside the fetchArticles() function:

const button = document.querySelector("#load-articles");
const status = document.querySelector("#article-status");
const results = document.querySelector("#article-results");

button.addEventListener("click", async () => {
  button.disabled = true;
  status.textContent = "Loading articles...";
  results.replaceChildren();

  try {
    const articles = await fetchArticles();

    for (const article of articles) {
      const item = document.createElement("li");
      item.textContent = article.title;
      results.append(item);
    }

    status.textContent = articles.length
      ? `${articles.length} articles loaded.`
      : "No articles found.";
  } catch (error) {
    console.error("Article loading failed:", error);
    status.textContent =
      "Unable to load articles. Please try again.";
  } finally {
    button.disabled = false;
  }
});

The button is available again after success or failure, allowing a retry. Using textContent displays titles as text rather than interpreting API values as HTML.

This is an integration example, not a standalone demo: it requires the endpoint described above.

Avoid unnecessary requests

  • Debounce live search: Wait briefly after typing before starting a request.
  • Handle obsolete responses: Cancel requests or track their identity so old results cannot replace newer ones.
  • Cache deliberately: Decide how long data remains useful and when to refresh it.

A loading indicator improves feedback. It does not make the request faster.

Before shipping

Test a successful response, an empty array, an HTTP error, invalid JSON and an unexpected data structure.

For cross-origin requests, the server must permit the browser’s access through appropriate CORS configuration. Switching from Fetch to Axios does not bypass that policy.

Keep secret API credentials on your backend. Anything delivered to browser JavaScript can be inspected.

A dependable API integration checks both the response and its meaning, then gives the user a clear next step when something goes wrong.

Further reading: MDN: Using the Fetch API.

No comments