If you’re building anything on the web—whether it’s a small frontend project or a production-ready app—you’ll eventually need to fetch data from an API. And for that, fetch() is one of the most simple & essential tools in JavaScript.
In this post, we’ll walk through how fetch() works, common mistakes, how to use it correctly, and tips for writing cleaner, more reliable code in real projects.
What is fetch() ?

fetch() is a built-in JavaScript function that lets you make HTTP requests.
It returns a Promise and replaces older methods like XMLHttpRequest.
Here’s the basic syntax:
fetch(url, options)
.then(response => response.json())
.then(data => {
// Do something with the data
})
.catch(error => {
// Handle network errors
});
- url: the endpoint you want to call
- options: (optional) an object for method, headers, body, etc.
Basic Usage Examples
1. GET request
This is the most basic use — fetching data from an API.
fetch('https://jsonplaceholder.typicode.com/posts/1')
.then(res => res.json())
.then(data => console.log(data.title));
This example fetches a single post from a fake API and logs the title.
2. POST request
fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
title: 'Hello Fetch',
body: 'This is a test post.',
userId: 1,
}),
})
.then(res => res.json())
.then(data => console.log(data));
- You set ‘Content-Type’ to ‘application/json’
- You use JSON.stringify() for the body
Error Handling
The Fetch API does not throw an error for HTTP errors by default. To handle them properly:
async function getPost() {
try {
const res = await fetch('https://api.example.com/post/1');
if (!res.ok) {
throw new Error(`Server error: ${res.status}`);
}
const data = await res.json();
console.log(data);
} catch (err) {
console.error('Fetch failed:', err);
}
}
Common mistakes
1. fetch() doesn’t throw errors on 404 or 500
fetch('/not-found')
.then(res => {
if (!res.ok) throw new Error('Server error');
return res.json();
})
.catch(err => console.error(err));
fetch() only throws an error for network-level issues (e.g., DNS failure, CORS).
HTTP status errors like 404 or 500 won’t trigger .catch() you need to check res.ok
2. Response body can only be read once
const res = await fetch(...);
const data = await res.json(); // OK
const text = await res.text(); // Error: body already used
You can either read the body as .json(), .text(), or .blob(), but only once.
3. CORS issues
Cross-origin requests may be blocked by the server unless it explicitly allows them via headers like:
Access-Control-Allow-Origin: *
If you’re calling an external API and hitting CORS errors, it’s likely the server doesn’t allow public access.
4. Cookies are not included by default
fetch('/api/user', {
credentials: 'include',
});
Use the credentials
option if your app uses session-based auth (e.g., with cookies).
- ‘omit’ (default): don’t send cookies
- ‘same-origin’: send cookies for same-origin requests
- ‘include’: send cookies always (even cross-origin)
5. No built-in timeout
fetch() does not timeout on its own. Use AbortController to cancel a long request:
const controller = new AbortController();
setTimeout(() => controller.abort(), 5000); // 5s timeout
fetch('/slow-api', { signal: controller.signal })
.catch(err => {
if (err.name === 'AbortError') console.log('Request timed out');
});
✔️ async/await vs then/catch
You can use either style with fetch. async/await is cleaner for complex flows:
async function loadData() {
try {
const res = await fetch('/api/data');
if (!res.ok) throw new Error('Something went wrong');
const json = await res.json();
console.log(json);
} catch (err) {
console.error('Error:', err);
}
}
fetch vs axios
Feature | fetch | axios |
---|---|---|
Built-in | ✅ Yes | ❌ Requires install |
Auto JSON parsing | ❌ No (use .json()) | ✅ Yes |
Timeout support | ❌ No | ✅ Yes |
Interceptors | ❌ No | ✅ Yes |
Cookie handling | Manual (credentials) | Automatic |
Test APIs for Practice
Here are two free APIs you can use while learning fetch:
Great for testing GET and POST requests without setting up a backend.