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.