Fetch API

The Fetch API is the standard way to make HTTP requests in JavaScript. It replaces the older XMLHttpRequest approach with a cleaner, Promise-based interface.

GET request

The default request method is GET. Pass a URL to fetch() and it returns a Promise that resolves to a Response object.

fetch('https://api.github.com/users')
  .then((response) => response.json())
  .then((data) => console.log(data))
  .catch((err) => console.error(err));

POST request

To send data, pass a second argument to fetch() with the method, headers, and body.

const options = {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ title: 'New post', body: 'Post content' }),
};

fetch('https://jsonplaceholder.typicode.com/posts', options)
  .then((response) => response.json())
  .then((data) => console.log(data));

Set Content-Type to application/json when sending JSON. Without it, the server may not parse the body correctly.

The Response object

fetch() resolves to a Response object. It contains information about the server’s response.

fetch('https://api.github.com/users').then((response) => {
  console.log(response.status);     // 200
  console.log(response.statusText); // OK
  console.log(response.ok);         // true (status 200-299)
  console.log(response.url);        // the URL that was fetched
});

To extract the body, call one of these methods (each returns a Promise):

  • response.json(): parse the body as JSON
  • response.text(): parse the body as plain text
  • response.blob(): for binary data like images

Handling errors

fetch() only rejects its Promise on network failure (no internet connection, DNS error). An HTTP error like a 404 or 500 does not cause a rejection; the Promise still resolves, but response.ok is false.

This surprises most beginners. Always check response.ok before parsing the body.

async function getUsers() {
  try {
    const response = await fetch('https://api.github.com/users');
    if (!response.ok) {
      throw new Error(`HTTP error: ${response.status}`);
    }
    const data = await response.json();
    return data;
  } catch (err) {
    console.error('Failed to fetch users:', err.message);
  }
}

Check response.ok first, then call response.json(). If you call .json() on an error response, you may get the server’s error body or a parse error, and neither is what you want.

Sending headers

Use the headers option to send additional headers, such as an authorization token.

const response = await fetch('/api/data', {
  headers: {
    'Authorization': 'Bearer my-token',
    'Content-Type': 'application/json',
  },
});

Using async/await

async/await makes fetch code easier to follow, especially when chaining multiple requests.

async function fetchData() {
  try {
    const response = await fetch('https://api.github.com/users');
    if (!response.ok) {
      throw new Error(`HTTP error: ${response.status}`);
    }
    const data = await response.json();
    console.log(data);
  } catch (err) {
    console.error('Fetch failed:', err.message);
  }
}

fetchData();

Cancelling a request

Use AbortController to cancel a fetch request. This is useful for search-as-you-type, where you want to cancel the previous request when the user types a new character.

const controller = new AbortController();

fetch('/api/data', { signal: controller.signal })
  .then((res) => res.json())
  .then((data) => console.log(data))
  .catch((err) => {
    if (err.name === 'AbortError') {
      console.log('Request was cancelled');
    } else {
      console.error('Fetch error:', err.message);
    }
  });

// Cancel the request before it completes
controller.abort();

FAQ

Why doesn’t fetch() throw on 404 errors?

fetch() only rejects when the network itself fails (you are offline, the DNS lookup fails, the connection is refused). A 404 is a valid HTTP response from the server. The request completed successfully; the server just replied with “not found.” Always check response.ok to catch HTTP errors.

How do I send a JWT token with my request?

Add an Authorization header with the token:

const token = 'your-jwt-token';

const response = await fetch('/api/protected', {
  headers: {
    Authorization: `Bearer ${token}`,
  },
});
  • async/await : the cleanest way to write fetch logic
  • Promises : what fetch() returns under the hood
  • AJAX : the concept behind HTTP requests in the browser