JavaScript Fetch API
Master the Fetch API for making HTTP requests in JavaScript, including GET, POST, error handling, and working with JSON responses.
TL;DR
- Use
fetch()to make HTTP requests and await the response. - Check
response.ok, since fetch rejects only on network failure. - Use async/await with
try/catchfor clean, readable fetch code.
Basic GET Request
fetch()Returns a Promise that resolves to a Response object, the entry point for every request.
async function getUser(id) {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
return response.json();
}response.okTrue for status codes 200-299 — always check it before trusting the response.
response.json()Parses the response body as JSON and returns another Promise.
const data = await response.json();Network-only rejectionfetch only rejects on network-level failures; a 404 or 500 still resolves successfully.
POST Request with JSON Body
Sending JSONSet method, headers, and body in the options object to send data.
async function createPost(data) {
const response = await fetch('/api/posts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
if (!response.ok) throw new Error('Request failed');
return response.json();
}methodSets the HTTP verb, like 'POST', 'PUT', or 'DELETE'.
headersSets request headers, most commonly Content-Type for JSON bodies.
headers: { 'Content-Type': 'application/json' }bodyHolds the request payload, usually JSON.stringify(data) for JSON APIs.
body: JSON.stringify(data)Error Handling Patterns
Robust try/catchHandle both network errors and HTTP error responses in one function.
async function safeFetch(url, options = {}) {
try {
const res = await fetch(url, options);
if (!res.ok) {
const msg = await res.text();
throw new Error(`${res.status}: ${msg}`);
}
return await res.json();
} catch (err) {
console.error('Fetch failed:', err);
throw err;
}
}Network-level errorsWrap fetch in try/catch to catch network-level errors like being offline or CORS failures.
HTTP error statusCheck response.ok inside the try block to catch HTTP errors (4xx, 5xx).
Reading the error bodyRead res.text() or res.json() on error responses to get the server's error message.
const msg = await res.text();Common Fetch Patterns & Fetch vs Alternatives
Auth tokenSend a bearer token header.
Authorization: `Bearer ${token}`Form dataSend a FormData body directly — no Content-Type header needed.
body: new FormData(formEl)Abort a requestCancel an in-flight request with AbortController.
const ac = new AbortController();
fetch(url, { signal: ac.signal });Read plain textRead a non-JSON body as text.
const text = await response.text();fetch()Best for simple requests with no extra dependencies; error handling is more verbose and there are no interceptors.
axiosBest for complex apps needing interceptors and retries; adds an external dependency of about 15 kB gzipped.
SWR / React QueryBest for data fetching in React with built-in caching; framework-specific and needs more setup.
tRPCBest for full-stack TypeScript with end-to-end types; requires a matching server setup.
Tips
- In Next.js App Router, prefer the built-in extended
fetch, since it supportscacheandrevalidateoptions for data fetching. - Always wrap fetch calls in try/catch and check response.ok, so both network failures and HTTP error responses get handled consistently.
- Use an
AbortControllerto cancel in-flight requests on unmount, which prevents wasted network calls and stale state updates.
Warnings
- Calling
response.json()on an error response that returns HTML, like a 404 page, throws a JSON parse error. - Use
res.text()as a safe fallback when the content type of a response is unknown or unconfirmed. - Forgetting to set the
Content-Typeheader on a POST request can cause the server to misparse the JSON body.
In Practice
Combines AbortController, try/catch, and a response.ok check to fetch data that gives up after a timeout instead of hanging forever.
- The AbortController's signal is passed to fetch, giving the request a way to be cancelled mid-flight.
- setTimeout calls controller.abort() if the response doesn't arrive within timeoutMs.
- clearTimeout in finally cancels the pending timer once the request settles, whether it succeeded, failed, or timed out.
async function fetchWithTimeout(url, timeoutMs = 5000) {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await fetch(url, { signal: controller.signal });
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
return await response.json();
} catch (error) {
if (error.name === 'AbortError') {
throw new Error('Request timed out');
}
throw error;
} finally {
clearTimeout(timer);
}
}
fetchWithTimeout('/api/users').then(data => console.log(data));FAQ
fetch() only rejects its promise on network-level failures (e.g., no internet, DNS error). HTTP error status codes like 404 or 500 resolve successfully. You must check response.ok or response.status manually before treating the response as valid.
Set the method to 'POST', add a 'Content-Type': 'application/json' header, and pass JSON.stringify(yourData) as the body. Without the Content-Type header, many servers won't parse the payload correctly.
Wrap your fetch call in a try/catch to handle network failures. Inside the try block, explicitly check response.ok and throw a new Error for HTTP errors. This lets your catch block handle both failure types in one place.
Yes — Next.js App Router extends the native fetch with cache and next.revalidate options. This lets you control caching and incremental static regeneration directly in server components without any additional library.