The Easy Guide to Using the Fetch API in JavaScript
Mastering the Ins and Outs of this Modern Async Approach
The fetch API is one of the most useful additions to JavaScript in recent years. It provides a modern, promise-based mechanism for making async requests. Whether you need to retrieve data from an API or upload files, fetch makes it straightforward.
In this article, we’ll cover everything you need to know to start using the Fetch API effectively today. We’ll look at how it works under the hood, go through plenty of examples, and also explain how to handle errors and nuances. By the end, you’ll feel comfortable harnessing the power of fetch in your own projects.
Let’s get started!
Fetch API 101
The fetch API allows you to make network requests to retrieve resources from a server. This could be an API that returns JSON data, an image, file, HTML page, literally anything.
Here is a simple GET request using fetch to retrieve data from a URL:
fetch('https://api.example.com/data')
.then(response => {
// Code for handling response
})Fetch uses promises under the hood, so it has a familiar .then() syntax for handling the response asynchronously.
Now let’s break this down…
The fetch Method
The main mechanism is the fetch() method which takes in the path to the resource you want to fetch. This could be an absolute URL like above, or a relative path.
You call fetch() and pass it the URL to get back a promise containing the response. This promise will resolve to a Response object when headers are received, before any actual body content.
So fetch gives you fine-grained control through promises — you attach then() to handle the response, catch errors, etc.
Fetch Options
The fetch method also allows an optional second parameter which is an init object containing settings like:
fetch('http://example.com', {
method: 'POST',
mode: 'cors',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({name: 'HubSpot'})
})So with the init object, you can configure things like:
- HTTP method (GET, POST, PUT, DELETE, etc.)
- Headers — Authorization, Content-Type, etc.
- CORS mode — Should it redirect CORS requests?
- Body — What body content to send with request for POST, PUT
And much more. We’ll see some of these in action later.
Handling Fetch Responses
Now back to our first example. We make the fetch request, get back a promise, and attach then() to handle the response:
fetch('http://example.com/data')
.then(response => {
// Code for handling response
})Inside the then(), the response object contains properties like:
- status — The HTTP response status code
- statusText — The status text like “OK” or “Not Found”
- headers — Response headers
- url — The final URL after redirects
- And more…
To extract the body content from the response, we use another promise method like:
fetch('http://example.com/data')
.then(response => response.json())
.then(data => {
// Code for handling `data` object
})So we chain on another .then() to call .json() which parses JSON response content and returns another promise with the JS object. For other types of data, you would call:
- .json() — For parsing JSON responses
- .text() — For text content
- .blob() — For Blob/File content
- .arrayBuffer() — For ArrayBuffer raw binary content
- etc.
And all return promises so they can be chained.
Now let’s walk through some Fetch API examples to see it applied…
Fetch API Examples
Get Request
Let’s see a complete GET request example with fetch:
fetch('https://api.example.com/items')
.then(res => res.json())
.then(data => {
console.log(data)
})
.catch(err => {
console.log('Error:', err)
})Here we:
- Call fetch() passing the URL to the API endpoint
- On fulfillment, parse the response with .json()
- Log the data when it resolves
- Handle errors in the catch()
Simple, clean syntax with promises.
Post JSON Data
For posting JSON data, we pass an init object as the second param:
const body = {title: 'Foo', body: 'Bar'};
fetch('https://api.example.com/items', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(body)
})
.then(res => res.json())
.then(data => console.log(data))
.catch(err => console.log(err))We configure the init object with properties like:
- method — The request method
- headers — object with headers to add
- body — The data to post converted to string
This allows us to make a POST request with fetch() configured completely.
File Upload
For uploading File and Blob data, we can use the FormData API:
const formData = new FormData();
formData.append('file', fileInput.files[0]);
fetch('https://api.example.com/upload', {
method: 'POST',
body: formData
});We create a FormData object, append our file with .append(), and pass {body: formData} as the second parameter of fetch().
The content-type will automatically be set to multipart/form-data which works nicely!
Fetch With Async/Await
The fetch API lends itself very nicely to async/await syntax:
async function fetchData() {
try {
const response = await fetch('https://api.example.com/items');
const data = await response.json();
console.log(data);
} catch(err) {
console.log('Error:', err);
}
}
fetchData();Using async/await allows us to work with promises in a cleaner syntax where:
- Code reads top-to-bottom without lots of callbacks
- We can use regular try/catch blocks for errors
- Easy to understand and maintain!
So I highly recommend using async/await with fetch for simplified async logic.
Fetching From Multiple Sources
A common need is fetching data from multiple URLs, aggregating responses, and handling everything. The JavaScript Promise.all method helps simplify this pattern beautifully:
async function get() {
const [userRes, postsRes] = await Promise.all([
fetch('/user'),
fetch('/posts')
]);
const user = await userRes.json();
const posts = await postsRes.json();
return {user, posts};
}
const data = await get();Promise.all allows you to pass an array of promises, wait for all to resolve, and get access to the aggregated resolved values.
We await both fetch requests in parallel, convert the responses, and return the parsed user + posts in a single shot without nesting. Awesome!
Handling Fetch Errors
It’s always important to handle errors properly. For fetch, network errors will automatically reject the promise:
fetch('https://api.example.com/items')
.then(res => {
// Handle response
})
.catch(err => {
console.log('Fetch Error :-S', err);
});So any network errors will trigger the catch().
For HTTP errors, the promise resolves but response.ok will be false if status is 400 or above:
fetch('https://api.example.com/items')
.then(res => {
if (!res.ok) {
throw new Error('Bad status code');
}
return res.json()
})
.then(data => {
console.log(data);
})
.catch(err => {
console.log('Error:', err);
});Here we check response.ok and throw an error to trigger the catch().
This ensures we handle both async errors and HTTP errors properly.
Fetch Polyfill
One thing to note is that the fetch API requires a polyfill to work on some older browsers like IE.
So you may want to include something like this to ensure wide browser support:
import fetch from 'cross-fetch';
fetch('https://api.example.com')
.then(...)The cross-fetch polyfill replicates the fetch API exactly for older browsers, so we can use fetch freely without worrying about legacy JavaScript environments.
Conclusion
The Fetch API provides an excellent promise-based method for working with resources asynchronously in modern browsers. With fetch and other new web APIs, development feels simpler and more streamlined.
We looked at how to make various requests like GET, POST, file uploads with fetch as well as techniques for handling errors and complex flows using Promise.all and async/await.
I hope this provides a solid foundation for applying the fetch API in your projects.






