JavaScript Fetch API: A Complete Overview
As web applications become more dynamic, efficient server communication is crucial. JavaScript's fetch
API provides a powerful and flexible way to handle HTTP
requests, allowing developers to interact with APIs and other remote resources. Mastering the fetch
API is essential for modern web development, whether you are retrieving, sending, updating, or deleting data.
In this article, you will discover how to use the fetch
API, from the basics to advanced techniques, by building an awesome webpage called Get Country Info! This will help you write efficient and maintainable code. Plus, we'll include a link to external APIs at the end of this article.
Prerequisites
Essential knowledge of JavaScript (arrays, functions, methods & objects)
Basic understanding of Application Programming Interfaces (APIs)
Basic knowledge of HTML/CSS
A text editor (VS code recommended)
A browser (Chrome recommended)
What is Fetch API?
The fetch
API is a modern JavaScript interface that lets developers make HTTP
requests, like getting or sending data, to and from web servers. It offers a cleaner and more powerful way to handle asynchronous requests compared to older methods like XMLHttpRequest
. The fetch
API was introduced in ECMAScript 6 (ES6) in 2015.
The fetch()
function takes two arguments: the resource URL (required) and an optional options object for configuring settings like the HTTP method, headers, and body. It returns a Promise that resolves to a Response object, regardless of whether the HTTP status code indicates success or failure. It's important to manually check the response.ok
property to determine if the request was successful.
Now you might wonder, what exactly is a Promise? A Promise
in JavaScript is an object that represents the eventual outcome (success or failure) of an asynchronous operation. It allows you to handle tasks like data fetching or file reading without stopping the rest of the code from running.
Promises help you manage asynchronous operations cleanly, avoiding "callback hell" (nested callbacks) and making your code easier to read and maintain. You can chain actions with .then()
for success and .catch()
for errors. Simply put, a promise is a placeholder for future values.
Note: Asynchronous operations are tasks that run independently of the main program flow, without blocking the execution of other parts of the code. This means they can run in the background while the rest of the program executes.
Key Features of Fetch API:
Promise-based: Fetch returns a Promise, making it easier to handle success and failure using
.then()
and.catch()
.Supports all HTTP methods: You can use
GET
,POST
,PUT
,DELETE
, etc., for various requests.Readable and maintainable: The syntax is simpler and more intuitive than previous methods.
Built-in support for handling responses: You can easily work with different data formats like
JSON
,text
,Blob
, etc.
Example of a GET
request:
fetch('https://api.example.com/data', {options}) //GET request, returns a promise
.then(response => response.json()) //Convert the response to JSON
.then(data => console.log(data)) //Log data to the console
.catch(error => console.error('Error:', error)); //Catch and log errors
The code snippet above illustrates how to send a GET
request to an external web API, handle the response, parse JSON
data, and log the results or errors to the console. This helps developers build efficient, interactive web applications by managing server communication seamlessly.
Example of a POST
request:
fetch('https://api.example.com/submit', {
method: 'POST', // Specify the HTTP method as POST
headers: {
'Content-Type': 'application/json', // Set the request headers to indicate JSON format
},
body: JSON.stringify({ username: 'johndoe', password: '123456' }) // Send data as JSON
})
.then(response => response.json()) // Convert the response to JSON
.then(data => console.log(data)) // Log the response data
.catch(error => console.error('Error:', error)); // Log any errors
Note: By default, the fetch()
function uses the GET
method to retrieve data from the server unless a different method (e.g., POST
, PUT
, DELETE
) is explicitly defined in the options object.
Creating a basic HTML/CSS Page
Now that we understand the fetch
API, let's dive in and put it to use! We'll start by building a simple HTML page with a search input and button to fetch data from an external API based on the user's search. This task will involve using the fetch
API to retrieve country data, chaining methods for efficient processing, handling errors, and effectively displaying the fetched country details. Let’s get started!
<body>
<section class="container">
<h1>Country Info Finder</h1>
<!-- Search Input -->
<div class="search-box">
<input
type="text"
id="country-input"
placeholder="Enter a country name"
/>
<button id="search-btn">Search</button>
</div>
<!-- Country Information Section -->
<div class="country-info">
<div id="error-message"></div>
<div id="result">
<h2 id="country-name"></h2>
<img id="country-flag" alt="Country Flag" />
<p><strong>Capital:</strong> <span id="capital"></span></p>
<p><strong>Population:</strong> <span id="population"></span></p>
<p><strong>Region:</strong> <span id="region"></span></p>
<p><strong>Currency:</strong> <span id="currency"></span></p>
</div>
</div>
</section>
</body>
Now for the CSS, let's apply some basic styling while initially hiding the result section.
.container {
width: 400px;
margin: 50px auto;
text-align: center;
font-family: Arial, sans-serif;
}
.search-box {
margin-bottom: 20px;
}
#country-input {
width: 70%;
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
}
#search-btn {
padding: 10px;
cursor: pointer;
border: none;
border-radius: 4px;
background-color: rgb(68, 66, 213);
color: #f9f9f9;
}
.country-info {
background-color: #f9f9f9;
padding: 20px;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
#country-flag {
width: 100px;
margin: 10px 0;
}
#error-message {
color: red;
font-weight: bold;
margin: 10px 0;
}
#result {
display: none;
}
The #result
is initially hidden using display: none;
to prevent the country information from being shown when the page loads or before the user performs a search. The JavaScript code updates the content once a user searches for a country and valid data is fetched from the API. It makes the #result
visible by changing its display property, ensuring the user only sees it after a successful search.
The web app doesn't work because the JavaScript hasn't been implemented.
Making a Basic GET Request
In this section, we’ll make a GET
request to an external API, display the fetched country information, and log them to the console.
// Function to fetch country data from an external web API
const fetchCountryData = (country) => {
fetch(`https://restcountries.com/v3.1/name/${country}`)
.then((response) => response.json())
.then((data) => console.log(data));
};
fetchCountryData('France'); //calling function
This code snippet defines a function, fetchCountryData
, that retrieves information about a country. It takes a single argument, country
, which specifies the desired nation.
Here's a breakdown of its functionality line by line for better understanding:
const fetchCountryData = (country) => { ... }
This line establishes a function named fetchCountryData
that accepts one parameter, country. The arrow (=>
) signifies it's a concise arrow function.
fetch("https://restcountries.com/v3.1/name/${country}")
This line utilizes the Fetch API to initiate a request to the external API at https://restcountries.com
. The URL cleverly includes a placeholder, ${country}
, which will be dynamically replaced with the actual country name you provide.
The entire fetch
call is wrapped in a chain of .then
methods. These methods handle the asynchronous nature of the request smoothly.
.then((response) => response.json())
This line of code deals with the response received from the API. It verifies if the response
is successful and subsequently transforms the response
body (potentially in JSON format) into a JavaScript object using .json()
.
.then((data) => console.log(data))
This final .then
method receives the parsed data, which holds the country information, as an argument named data
. Here, it simply prints the data to the console for display using console.log
.
fetchCountryData('France');
This line calls the fetchCountryData
function and passes the string "France" as the argument. This starts the process of fetching data about France from the specified web API and logging the retrieved information to the console.
In essence, this code snippet shows a simple way to fetch country data from an external API and display it in the console. It doesn't explicitly handle errors, but you can easily add a .catch
block to manage any potential issues.
Handling Errors in GET Requests
When making GET
requests, it's important to be ready for possible errors. These errors can happen due to network issues, server problems, or invalid requests. Adding good error-handling methods allows you to manage these situations smoothly and improve the user experience.
This section will explore different strategies for handling errors in GET
requests, such as using the .catch
method, checking response status codes, and providing informative error messages.
.then((response) => {
if (!response.ok) {
throw new Error('Country not found');
}
return response.json();
This code snippet above first checks if the server response is successful by evaluating response.ok
. If the response indicates a failure (such as a 404
or 500
status code), the code throws an error using throw new Error('Country not found')
. This stops further execution in the current .then()
block and transfers control to the .catch()
block for error handling. If the response is successful, it returns the data as JSON
using response.json()
for further processing.
The throw new Error()
statement is used to generate an error in the code manually. In this case, it is triggered when the server responds with a non-OK status (such as an HTTP 404 error
). When this error is thrown, it creates a custom error object with the message 'Country not found'.
This error then causes the promise to be rejected, skipping the remaining code in the .then()
chain and moving directly to the .catch()
block, where the error is handled (e.g., displaying an error message to the user). This ensures that failed requests are properly managed.
.catch((error) => {
console.error('Error:', error.message);
});
The .catch()
block handles any error that occurs during the fetch request. If an error is thrown (like a failed network request or invalid response), the code inside .catch()
is executed. In this example, it logs the error message to the console using console.error()
, which helps identify issues in the request or response process.
The error.message
property provides a human-readable description of the error that occurred. In the .catch()
block, error.message
is used to retrieve and display the specific reason for the error, such as "Country not found" or a network issue, making it easier to understand what went wrong during the fetch request.
Now that we understand how error handling works, let's update our code.
// Function to fetch country data from an external API
const fetchCountryData = (country) => {
fetch(`https://restcountries.com/v3.1/name/${country}`)
.then((response) => {
if (!response.ok) {
throw new Error('Country not found');
}
return response.json();
})
.then((data) => {
console.log(data); // Log the fetched data for the specified country
// You can process and use this data as needed
})
.catch((error) => {
console.error('Error:', error.message); // Handle any errors during fetch
});
};
fetchCountryData('France');
This code includes an error-handling function to ensure a smooth user experience and better code readability.
Get Country Info App
Now that we're familiar with the fetch
API, let's refactor our code to make it more functional. We'll start by bringing in the elements from the HTML file.
// Get the HTML elements
const searchBtn = document.getElementById('search-btn'),
countryInput = document.getElementById('country-input'),
countryName = document.getElementById('country-name'),
countryFlag = document.getElementById('country-flag'),
capital = document.getElementById('capital'),
population = document.getElementById('population'),
region = document.getElementById('region'),
currency = document.getElementById('currency'),
resultDiv = document.getElementById('result'),
errorMessage = document.getElementById('error-message');
Inside the fetchCountryData()
function, just before making the GET
request, let's get the user input and remove any extra spaces. We'll also set the error message and display to none. This helps handle any unexpected errors and clears previous error messages and results.
const country = countryInput.value.trim();
if (country === ' ') {
errorMessage.textContent = 'Please enter a country name';
resultDiv.style.display = 'none';
return;
}
// Clear previous error messages and results
errorMessage.textContent = ' ';
resultDiv.style.display = 'none';
Next, we refactor the second .then()
method that contains the result data. We get the necessary country details, assign them, and display them in the browser.
.then((data) => {
const countryData = data[0]; // Get the first result
// Get currency details (first currency listed)
const currencyCode = Object.keys(countryData.currencies)[0];
const currencyName = countryData.currencies[currencyCode].name;
const currencySymbol = countryData.currencies[currencyCode].symbol;
// Display the country information
countryName.textContent = countryData.name.common;
countryFlag.src = countryData.flags.png;
capital.textContent = countryData.capital[0];
population.textContent = countryData.population.toLocaleString();
region.textContent = countryData.region;
currency.textContent = `${currencyName} (${currencySymbol})`;
resultDiv.style.display = 'block'; // Show the results
})
This block of code above processes the fetched country data and updates the webpage with specific details. It initially extracts the first country result from the data array, then retrieves the currency information by finding the first currency listed and obtaining its name and symbol.
Next, it updates the HTML elements to display the country's name, flag, capital, population, region, and currency. Finally, it makes the result section visible by changing its display style, allowing the user to see the fetched information.
.catch((error) => {
errorMessage.textContent = 'Country not found. Please try again.';
resultDiv.style.display = 'none'; // Hide results if an error occurs
});
Let's update the error handling code with the code above. If an error occurs, it updates the error message element to display "Country not found. Please try again." Additionally, it hides the result section by setting its display style to none
, ensuring that no incorrect data is shown when an error occurs.
Now we add an event listener to the button, passing the callback function fetchCountryData
as an argument to ensure it works as planned.
// Add event listener to the search button
searchBtn.addEventListener('click', fetchCountryData);
Here we used fetchCountryData
without parentheses in searchBtn.addEventListener('click', fetchCountryData);
because you're passing the function itself as an argument to the addEventListener
method.
If you used fetchCountryData()
with parentheses, you would be calling the function right away and passing the result to addEventListener
. This is not what you want, as you want the function to be called only when the button is clicked.
This is the updated code and output. You can test it below:
For mobile: best viewed with 0.5x.
Working with Async/Await in Fetch API
async/await
is a modern approach to managing asynchronous tasks in JavaScript. While the .then()
method is good for chaining promises, it can make the code harder to read and manage, especially with multiple asynchronous operations.
async/await
simplifies working with promises by letting you write asynchronous code that looks and acts more like synchronous code. It removes the need for chaining .then()
methods, making the code easier to read and follow.
With async
, you declare that a function will handle asynchronous operations. By using await
within that function, you pause its execution until a promise is resolved. This makes the code more readable and reduces nesting, especially when dealing with multiple asynchronous tasks.
To declare an asynchronous function with the fetch
API, use the async
keyword before the function declaration. This shows that the function will handle asynchronous operations using promises.
The await
keyword is exclusively used within asynchronous functions. When you place await
before a promise, the function's execution will pause until the promise is resolved.
For example:
async function fetchData() {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
return data;
}
In this example, the fetchData()
function is defined as asynchronous using async
. The await
keyword pauses the function until the fetch
request is completed and the response is converted to JSON
. This makes the code simpler compared to using .then()
chains.
Error Handling with Async/Await
In async/await
functions, errors can be handled using a try...catch
block. This structure allows you to catch any expectations that might occur during the execution of the asynchronous code. If an error is encountered, the catch
block will be executed, providing an opportunity to handle the error gracefully. try...catch
blocks can make your asynchronous functions more robust and resilient to potential errors.
Example:
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
return data;
} catch (error) {
console.error('Error fetching data:', error.message);
}
}
Note: catch
in try…catch
is a block that automatically catches any error thrown inside the try
block, including await
operations. The catch
block doesn’t need an arrow function because it is not a callback or a function itself—it's just a control structure that will execute when an error is thrown.
Conclusion
Congratulations on reaching this milestone—your new skill with the fetch
API unlocks endless possibilities for creating dynamic, data-driven web experiences! Mastering the fetch
API is a big achievement in modern web development. With its simplicity and power, fetch
changes how we handle HTTP
requests, providing a more streamlined solution than older methods like XMLHttpRequest
.
Using fetch
alongside async…await
and robust error handling, you can now build more efficient, responsive, and reliable applications. Feel free to refactor the code we created using the async…await
functions. In the next article, we’ll explore the POST
method, headers
, and the common pitfalls to avoid when using the fetch
API.
More Resources
References:
Happy Coding! ✨
Subscribe to my newsletter
Read articles from Graham Boyle directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Graham Boyle
Graham Boyle
A Front-end Engineer and Technical Writer