Fetching JSON from APIs is one of the most common tasks in JavaScript development. This guide covers everything from basic fetch calls to error handling, loading states, and real-world patterns.

Basic fetch with async/await

const response = await fetch('https://api.example.com/users');
const data = await response.json();
console.log(data);

Always check the response status

async function fetchUsers() {
  try {
    const response = await fetch('https://api.example.com/users');

    // fetch() only rejects on network failure, NOT on 4xx/5xx
    if (!response.ok) {
      throw new Error(`HTTP error: ${response.status} ${response.statusText}`);
    }

    const data = await response.json();
    return data;
  } catch (error) {
    console.error('Fetch failed:', error.message);
    throw error;
  }
}

POST JSON to an API

async function createUser(userData) {
  const response = await fetch('https://api.example.com/users', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': 'Bearer YOUR_TOKEN'
    },
    body: JSON.stringify(userData)
  });

  if (!response.ok) throw new Error('Failed to create user');
  return await response.json();
}

const newUser = await createUser({ name: 'Alice', email: 'alice@example.com' });

Fetch with timeout

async function fetchWithTimeout(url, timeoutMs = 5000) {
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), timeoutMs);

  try {
    const response = await fetch(url, { signal: controller.signal });
    clearTimeout(timeoutId);
    if (!response.ok) throw new Error('HTTP ' + response.status);
    return await response.json();
  } catch (error) {
    if (error.name === 'AbortError') throw new Error('Request timed out');
    throw error;
  }
}

React hook pattern

import { useState, useEffect } from 'react';

function useJSON(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    let cancelled = false;

    async function fetchData() {
      try {
        const res = await fetch(url);
        if (!res.ok) throw new Error('HTTP ' + res.status);
        const json = await res.json();
        if (!cancelled) setData(json);
      } catch (err) {
        if (!cancelled) setError(err.message);
      } finally {
        if (!cancelled) setLoading(false);
      }
    }

    fetchData();
    return () => { cancelled = true; }; // cleanup
  }, [url]);

  return { data, loading, error };
}

// Usage
function UserList() {
  const { data, loading, error } = useJSON('https://api.example.com/users');
  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error}</p>;
  return <ul>{data.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}

Common fetch mistakes

  • Not checking response.ok — fetch resolves on 404 and 500 too
  • Forgetting await on response.json() — returns a Promise, not data
  • No error handling — network failures throw but HTTP errors don't
  • Not setting Content-Type for POST requests with JSON body

Try it free — JSON Formatter

Format and inspect the JSON responses from your API calls.

Open tool →