JavaScript Fetch API explained simply

What is the Fetch API?

The Fetch API is a modern JavaScript interface for making HTTP requests. It’s built into modern browsers and provides a cleaner, more powerful way to fetch resources from the web compared to the older XMLHttpRequest.

Think of it like this: if you want to get data from a website or send data to a server, the Fetch API is your tool. It’s like having a messenger that can go to any URL, ask for information, and bring it back to your JavaScript code.

Why Use Fetch API?

  • Promise-based: Works seamlessly with async/await
  • Built-in: No need to install external libraries
  • Modern: Better error handling and more features
  • Flexible: Supports all HTTP methods (GET, POST, PUT, DELETE, etc.)

Basic Usage

The simplest way to use fetch is to make a GET request:

fetch('https://api.example.com/data')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error('Error:', error));

Or using async/await (cleaner syntax):

async function getData() {
  try {
    const response = await fetch('https://api.example.com/data');
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error('Error:', error);
  }
}

Making Different Types of Requests

GET Request (Default)

fetch('https://api.example.com/users')
  .then(response => response.json())
  .then(users => console.log(users));

POST Request

const newUser = {
  name: 'John Doe',
  email: 'john@example.com'
};

fetch('https://api.example.com/users', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify(newUser)
})
.then(response => response.json())
.then(result => console.log('Success:', result));

PUT Request

const updatedUser = {
  name: 'John Smith',
  email: 'johnsmith@example.com'
};

fetch('https://api.example.com/users/123', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify(updatedUser)
})
.then(response => response.json())
.then(result => console.log('Updated:', result));

DELETE Request

fetch('https://api.example.com/users/123', {
  method: 'DELETE'
})
.then(response => {
  if (response.ok) {
    console.log('User deleted successfully');
  }
});

Handling Responses

The fetch response object has several useful properties and methods:

fetch('https://api.example.com/data')
  .then(response => {
    // Check if request was successful
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    
    // Check response type
    const contentType = response.headers.get('content-type');
    
    // Parse based on content type
    if (contentType && contentType.includes('application/json')) {
      return response.json();
    } else {
      return response.text();
    }
  })
  .then(data => console.log(data))
  .catch(error => console.error('Error:', error));

Common Response Methods

  • response.json() - Parse JSON response
  • response.text() - Get response as text
  • response.blob() - Get response as blob (for files)
  • response.arrayBuffer() - Get response as array buffer
  • response.formData() - Get response as form data

Error Handling

Fetch only rejects on network errors, not HTTP errors. Here’s how to handle both:

async function fetchWithErrorHandling(url) {
  try {
    const response = await fetch(url);
    
    // Check if HTTP request was successful
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    
    return await response.json();
  } catch (error) {
    if (error.name === 'TypeError') {
      console.error('Network error:', error.message);
    } else {
      console.error('HTTP error:', error.message);
    }
    throw error;
  }
}

Working with Headers

You can set custom headers for your requests:

fetch('https://api.example.com/data', {
  headers: {
    'Authorization': 'Bearer your-token-here',
    'Content-Type': 'application/json',
    'Accept': 'application/json'
  }
});

Uploading Files

Use FormData to upload files:

const formData = new FormData();
formData.append('file', fileInput.files[0]);
formData.append('name', 'my-file');

fetch('https://api.example.com/upload', {
  method: 'POST',
  body: formData
})
.then(response => response.json())
.then(result => console.log('Upload successful:', result));

Canceling Requests

Use AbortController to cancel ongoing requests:

const controller = new AbortController();
const signal = controller.signal;

fetch('https://api.example.com/slow-data', { signal })
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => {
    if (error.name === 'AbortError') {
      console.log('Fetch aborted');
    }
  });

// Cancel the request after 5 seconds
setTimeout(() => controller.abort(), 5000);

Best Practices

  1. Always check response.ok: Don’t assume success
  2. Handle errors properly: Catch both network and HTTP errors
  3. Use async/await: Cleaner than .then() chains
  4. Set appropriate headers: Especially Content-Type for POST/PUT
  5. Cancel long-running requests: Use AbortController for user experience

Real-World Example

Here’s a complete example of a user management system:

class UserAPI {
  constructor(baseURL) {
    this.baseURL = baseURL;
  }

  async getUsers() {
    const response = await fetch(`${this.baseURL}/users`);
    if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
    return response.json();
  }

  async createUser(userData) {
    const response = await fetch(`${this.baseURL}/users`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(userData)
    });
    if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
    return response.json();
  }

  async updateUser(id, userData) {
    const response = await fetch(`${this.baseURL}/users/${id}`, {
      method: 'PUT',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(userData)
    });
    if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
    return response.json();
  }

  async deleteUser(id) {
    const response = await fetch(`${this.baseURL}/users/${id}`, {
      method: 'DELETE'
    });
    if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
    return true;
  }
}

// Usage
const userAPI = new UserAPI('https://api.example.com');

// Get all users
userAPI.getUsers()
  .then(users => console.log('Users:', users))
  .catch(error => console.error('Failed to get users:', error));

// Create a new user
userAPI.createUser({ name: 'Jane Doe', email: 'jane@example.com' })
  .then(user => console.log('Created user:', user))
  .catch(error => console.error('Failed to create user:', error));

Summary

The Fetch API is a powerful, built-in way to make HTTP requests in JavaScript. It’s promise-based, supports all HTTP methods, and provides excellent error handling. Whether you’re building a simple app or a complex web application, the Fetch API gives you the tools you need to communicate with servers effectively.

If this article was helpful, tweet it!