Tutorial 2.2.1

Consuming APIs: Fetch, Axios & Error Handling

Chapter 5 · Client‑Server Interaction
~2.5 hours Intermediate REST · GraphQL · Fetch · Axios

Overview

Modern web applications are built around APIs. This tutorial covers the essential skills for consuming APIs from the browser: understanding RESTful and GraphQL APIs, using the native Fetch API, the Axios library, handling errors gracefully, managing loading states, and cancelling requests with AbortController.

Why this matters: Every frontend application communicates with a backend. Knowing how to reliably fetch, post, and handle errors is a core skill for any developer.

1. REST vs GraphQL

REST (Representational State Transfer)

  • Resources: Each endpoint represents a resource (/users, /posts/1).
  • HTTP methods: GET (read), POST (create), PUT/PATCH (update), DELETE.
  • Stateless: Each request contains all needed information.
  • Advantages: Caching, standardised, widely understood.
  • Disadvantages: Over‑fetching (getting more data than needed) and under‑fetching (needing multiple endpoints).

GraphQL

  • Single endpoint: All queries go to /graphql.
  • Client‑specified queries: The client asks for exactly the fields it needs.
  • Strong typing: Schema defines available types and queries.
  • Advantages: No over‑/under‑fetching, multiple resources in one request.
  • Disadvantages: Complexity, caching is harder, learning curve.
// REST: GET /users/1 returns full user object // GraphQL: query { user(id: 1) { name, email } } returns only name and email

2. The Fetch API

fetch() is the native browser API for making HTTP requests. It returns a Promise that resolves to a Response object.

GET request

fetch('https://api.example.com/users') .then(response => { if (!response.ok) throw new Error('Network response was not ok'); return response.json(); }) .then(data => console.log(data)) .catch(error => console.error('Fetch error:', error));

POST request

fetch('https://api.example.com/users', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ name: 'Alice', email: 'alice@ex.com' }), }) .then(res => res.json()) .then(data => console.log('Created:', data));
Important: fetch does not reject on HTTP error status (e.g., 404, 500). You must check response.ok and throw manually.

3. Axios

Axios is a popular HTTP client that works in both browsers and Node.js. It provides a cleaner API, automatic JSON transformation, and better error handling.

Installation

npm install axios

GET request

import axios from 'axios'; axios.get('https://api.example.com/users') .then(response => console.log(response.data)) .catch(error => console.error('Axios error:', error));

POST request

axios.post('https://api.example.com/users', { name: 'Alice', email: 'alice@ex.com' }) .then(res => console.log(res.data));

Advantages of Axios over Fetch

  • Throws errors on HTTP error status (4xx, 5xx).
  • Automatic JSON parsing.
  • Request/response interceptors.
  • Timeouts and cancellation built‑in (though Fetch has AbortController).

4. Error Handling

Robust applications handle errors gracefully. Common strategies:

  • Network errors: No internet, DNS failure, CORS issues.
  • HTTP errors: 404 (Not Found), 401 (Unauthorized), 500 (Server Error).
  • Business logic errors: Validation failures, duplicates, insufficient funds.
// Axios example with error handling axios.get('/api/users') .then(res => setUsers(res.data)) .catch(error => { if (error.response) { // Server responded with a status code outside 2xx console.error('Server error:', error.response.status, error.response.data); } else if (error.request) { // Request was made but no response received console.error('No response:', error.request); } else { // Something else happened console.error('Error:', error.message); } setError('Failed to load users. Please try again.'); });

5. Loading States & Request Cancellation

Loading UI

Show a spinner or skeleton while the request is in flight. This improves perceived performance and user experience.

function UsersList() { const [loading, setLoading] = useState(true); const [users, setUsers] = useState([]); useEffect(() => { fetchUsers(); }, []); const fetchUsers = async () => { setLoading(true); try { const res = await axios.get('/api/users'); setUsers(res.data); } finally { setLoading(false); } }; if (loading) return <div>Loading...</div>; return <ul>{users.map(...)}</ul>; }

Request Cancellation (AbortController)

Cancel in‑flight requests when a component unmounts or when dependencies change, to prevent memory leaks and race conditions.

useEffect(() => { const controller = new AbortController(); axios.get('/api/users', { signal: controller.signal }) .then(res => setUsers(res.data)) .catch(err => { if (axios.isCancel(err)) { console.log('Request cancelled:', err.message); } else { setError(err.message); } }); return () => controller.abort(); // Cleanup on unmount }, []);

Quiz

Question 1

In a RESTful API, which HTTP method is typically used to create a new resource?

  • GET
  • POST
  • PUT
  • DELETE
Show answer
B. POST.

Question 2

Does the fetch API throw an error for a 404 response?

  • Yes, always
  • No, you must check response.ok
  • Only if the request fails
  • Depends on the browser
Show answer
B. No, you must check response.ok.

Question 3

Which method is used to cancel an in‑flight fetch request?

  • cancel()
  • abort()
  • stop()
  • clear()
Show answer
B. abort() via AbortController.

Exercises

Exercise 1

Write a function fetchUser(id) that uses fetch to get a user from /api/users/{id} and returns the parsed JSON. Handle errors by throwing a custom error message if the response is not OK.

Sample answer
async function fetchUser(id) {
  const response = await fetch(`/api/users/${id}`);
  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  }
  return await response.json();
}

Exercise 2

Convert the fetchUser function to use Axios instead. What differences do you notice?

Sample answer
import axios from 'axios';

async function fetchUser(id) {
  try {
    const response = await axios.get(`/api/users/${id}`);
    return response.data;
  } catch (error) {
    throw new Error(`Axios error: ${error.message}`);
  }
}

Differences: Axios throws on HTTP errors, so we don't need to check response.ok; the data is in response.data, not response.json().

Homework

Homework 1

Build a React component UserProfile that fetches a user by ID from /api/users/:id using Axios. It should show a loading spinner while fetching, display the user data when loaded, and show an error message if the request fails. Also cancel the request if the component unmounts.

Sample answer
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const controller = new AbortController();
    setLoading(true);
    setError(null);

    axios.get(`/api/users/${userId}`, { signal: controller.signal })
      .then(res => setUser(res.data))
      .catch(err => {
        if (!axios.isCancel(err)) setError(err.message);
      })
      .finally(() => setLoading(false));

    return () => controller.abort();
  }, [userId]);

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;
  return <div>{user.name} - {user.email}</div>;
}

Mini‑Project

Weather Dashboard

Build a React application that fetches weather data from a public API (e.g., OpenWeatherMap).

  • Input field for city name.
  • On submit, fetch weather data (temperature, humidity, condition).
  • Display the data in a styled card.
  • Show loading and error states.
  • Cancel the request if the user submits a new city before the previous request completes.
Sample implementation

Component structure:

function WeatherApp() {
  const [city, setCity] = useState('');
  const [weather, setWeather] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  const controllerRef = useRef(null);

  const fetchWeather = async (cityName) => {
    if (controllerRef.current) controllerRef.current.abort();
    const controller = new AbortController();
    controllerRef.current = controller;

    setLoading(true);
    setError(null);
    try {
      const res = await axios.get(`https://api.openweathermap.org/data/2.5/weather?q=${cityName}&appid=YOUR_API_KEY`, { signal: controller.signal });
      setWeather(res.data);
    } catch (err) {
      if (!axios.isCancel(err)) setError(err.message);
    } finally {
      setLoading(false);
    }
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    if (city.trim()) fetchWeather(city);
  };

  return ( ... );
}

Tutorial Summary

You learned how to communicate with APIs from a React application. We covered the differences between REST and GraphQL, the native Fetch API, the Axios library, robust error handling, loading states, and request cancellation. The exercises and mini‑project gave you practical experience in building resilient data‑fetching components.

Key takeaway: Always handle loading, error, and success states. Use modern tools like Axios and AbortController to keep your data‑fetching code clean and reliable.