Understanding the JavaScript Fetch API with Examples

JavaScript can send network requests to the server and load new information whenever it’s needed.

For example, we can use a network request to:

  • Submit an order,

  • Load user information,

  • Receive latest updates from the server,

  • …etc.

…And all of that without reloading the page!

There are multiple ways to send a network request and get information from the server.

The fetch API is modern, versatile and natively available, so I’ll discuss and implement it in detail in this article. I'll also shed light on some of the cool features it has to offer.

It’s not supported by old browsers (can be polyfilled), but very well supported among the modern ones.

Let's get started!

How does Fetch API work?

For basic HTTP requests, using fetch() is a three-step process:

  • Call fetch(), passing the URL whose content you want to retrieve.

  • Get the Response object that is asynchronously returned by step 1 when HTTP response begins to arrive and call a method of this Response object to ask for the body of the response (data).

  • Get the body object that is asynchronously returned by step 2 and process it however you want.

The fetch() API is completely Promise-based, and there are two asynchronous steps here, so you typically expect two then() calls or two await expressions when using fetch(). (And if you’ve forgotten what those are, you may want to reread this article.)

Here’s what a fetch() request looks like if you are using then() and expect the server’s response to your request to be JSON-formatted:

fetch("/api/users/current") // Make a GET request
  .then((response) => response.json()) // Parse its body as a JSON object
  .then((currentUser) => {
    // Then process that parsed object
    displayUserInfo(currentUser);
  });

Here’s a similar request made using the async and await keywords to an API that returns a plain string rather than a JSON object:

async function isServiceReady() {
  let response = await fetch("/api/service/status");
  let body = await response.text();
  return body === "ready";
}

If you understand these two code examples, then you know 80% of what you need to know to use the fetch() API. The next few sections that follow will demonstrate how to make requests and receive responses that are somewhat more complicated than those shown above.

HTTP Status Codes, Response Headers and Network Errors

The three-step fetch() process shown in the previous section omits all error-handling code. Here’s a more realistic version:

fetch("/api/users/current")
  .then((response) => {
    // Check if the response is okay and has the expected type.
    if (
      response.ok &&
      response.headers.get("Content-Type") === "application/json"
    ) {
      // If successful, return a Promise for the parsed JSON body.
      return response.json();
    } else {
      // If not successful, throw an error.
      throw new Error(
        `Unexpected response status ${response.status} or content type`
      );
    }
  })
  .then((currentUser) => {
    // When the Promise for the JSON body resolves, do something with the parsed body.
    displayUserInfo(currentUser);
  })
  .catch((error) => {
    // If anything goes wrong, log the error.
    // If the user's browser is offline, fetch() itself will reject.
    console.log("Error while fetching current user:", error);
  });

Let's break down the code point by point:

  • Promise Resolution by fetch():

    • The fetch() function returns a Promise that resolves to a Response object.
    • This Response object represents the response to the request made by fetch().
  • Partial Resolution of Promise:

    • fetch() resolves its Promise as soon as the server's response headers and HTTP status are available.
    • At this point, the full response body may not have arrived yet.
  • HTTP Status Code Check:

    • The status property of the Response object contains the HTTP status code.
    • The ok property is true if the status is between 200 and 299, indicating a successful request.
    • The ok property is false for any other status code.
  • Examination of Headers:

    • The headers property of the Response object is a Headers object.
    • You can use the has() method to test for the presence of a header.
    • Use the get() method to retrieve the value of a header.
    • HTTP header names are case-insensitive.
  • Parsing JSON Body:

    • The response.json() method is used to parse the response body as JSON.
    • This method returns a Promise that resolves to the JSON representation of the response body.
    • It reads the body of the response stream and parses it as JSON, transforming the raw data into a JavaScript object.
    • If the response body is not valid JSON, a SyntaxError is thrown, and the Promise is rejected.
  • Chaining the Next .then():

    • The response.json() method is typically used within the first .then() block of the Promise chain after the initial fetch.
    • Once the JSON parsing is successful, the next .then() block is executed, and it receives the parsed JSON as its argument.
    • In the provided code, the parsed JSON representing the current user is passed to the displayUserInfo function within the second .then() block.
  • Handling Promise Rejection:

    • Regardless of the server's response (e.g., 404 Not Found or 500 Internal Server Error), fetch() fulfils its Promise with a Response object.
    • fetch() rejects its Promise only if it cannot contact the web server.
    • Rejection can occur if the user's computer is offline, the server is unresponsive, or the specified URL has a non-existent hostname.
  • .catch() for Error Handling:

    • It is recommended to include a .catch() clause after a fetch() call to handle errors.
    • This ensures proper error handling for scenarios where the network request fails, and the Promise is rejected.

Specifying request header and request method

In each of the fetch() examples shown so far, we have made an HTTP (or HTTPS) GET request. If you want to use a different request method (such as POST, PUT, or DELETE), simply use the two-argument version of fetch(), passing an Options object with a method parameter:

fetch(url, {
  method: "POST",
})
  .then((response) => response.json())
  .then(handleResponse);

POST and PUT requests typically have a request body containing data to be sent to the server. As long as the method property is not set to 'GET' or 'HEAD' (which do not support request bodies), you can specify a request body by setting the body property of the Options object:

fetch(url, {
  method: "POST",
  body: "hello world",
});

When you specify a request body, the browser automatically adds an appropriate 'Content-Length' header to the request.

When the body is a string, as in the preceding example, the browser defaults the 'Content-Type' header to 'text/plain;charset=UTF-8.' You may need to override this default if you specify a string body of some more specific type such as 'text/html' or 'application/json':

fetch(url, {
  method: "POST",
  headers: new Headers({ "Content-Type": "application/json" }),
  body: JSON.stringify(requestBody),
});

Summary

A typical fetch request consists of two await calls:

let response = await fetch(url, options); // resolves with response headers
let result = await response.json(); // read body as json

Or, without await:

fetch(url, options)
  .then(response => response.json())
  .then(result => /* process result */)

Response properties:

  • response.status – HTTP code of the response,
  • response.ok – true if the status is 200-299.
  • response.headers – Map-like object with HTTP headers.

Methods to get response body:

  • response.text() – return the response as text,
  • response.json() – parse the response as JSON object,

Fetch options so far:

  • method – HTTP-method,
  • headers – an object with request headers (not any header is allowed),
  • body – the data to send (request body)

Get my free, weekly JavaScript tutorials

Want to improve your JavaScript fluency?

Every week, I send a new full-length JavaScript article to thousands of developers. Learn about asynchronous programming, closures, and best practices — as well as general tips for software engineers.

Join today, and level up your JavaScript every Sunday!

Thank you, Taha, for your amazing newsletter. I’m really benefiting from the valuable insights and tips you share.

- Remi Egwuda