What an API Actually Is
An API, Application Programming Interface, is simply a way for one program to ask another program for
data or to tell it to do something. When your weather app shows today's forecast, it did not calculate
that forecast itself, it sent a request to a weather service's API and received the current data back as a
response. Your job as a frontend developer is usually to request that data and display it, and
fetch() is the built-in browser tool for making that request.
Your First Fetch Request
fetch() takes a URL and returns a Promise, a JavaScript
object representing a value that is not ready yet but will be at some point, either successfully or with
an error. Let us request data from a free public API.
fetch('https://api.example.com/users')
.then(response => response.json())
.then(data => {
console.log(data);
})
.catch(error => {
console.error('Something went wrong:', error);
});
Walking through this chain: fetch() sends the request and resolves with a
Response object once the server replies. That response is not the actual
data yet, it is a wrapper around it, so calling .json() reads the body and
parses it as JSON, which itself returns another Promise. The second
.then() finally gives you the real, usable JavaScript data.
.catch() catches network failures, like the user losing internet
connection, along the way.
The Cleaner Way: async/await
Chained .then() calls work, but they get hard to read once you have more
than a couple of steps. Modern JavaScript offers async and
await, which let asynchronous code read almost like normal, top-to-bottom
code.
async function getUsers() {
try {
const response = await fetch('https://api.example.com/users');
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Something went wrong:', error);
}
}
getUsers();
Any function that uses await inside it must itself be marked
async. await pauses that function until
the Promise resolves, without freezing the rest of the page, and the surrounding
try/catch block replaces .catch() for
error handling. This is the pattern used in the vast majority of modern JavaScript projects.
A Complete Working Example
Let us fetch real data from a public testing API and render it into the page using DOM skills from our previous guide. This example uses JSONPlaceholder, a free fake API commonly used for practice.
<button id="loadBtn">Load Posts</button>
<ul id="postList"></ul>
const loadBtn = document.querySelector('#loadBtn');
const postList = document.querySelector('#postList');
async function loadPosts() {
loadBtn.textContent = 'Loading...';
loadBtn.disabled = true;
try {
const response = await fetch('https://jsonplaceholder.typicode.com/posts?_limit=5');
if (!response.ok) {
throw new Error(`Request failed with status ${response.status}`);
}
const posts = await response.json();
postList.innerHTML = ''; // clear any previous results
posts.forEach(post => {
const item = document.createElement('li');
item.textContent = post.title;
postList.appendChild(item);
});
} catch (error) {
postList.textContent = 'Failed to load posts. Please try again.';
console.error(error);
} finally {
loadBtn.textContent = 'Load Posts';
loadBtn.disabled = false;
}
}
loadBtn.addEventListener('click', loadPosts);
Notice everything working together here: the button disables itself while loading so a visitor cannot
trigger the request twice, response.ok is checked before trusting the
data, errors update the page instead of silently failing in the console, and
finally guarantees the button resets whether the request succeeded or
failed. This is what production-grade fetch code looks like, not just the happy path.
Checking response.ok, Not Just Catching Errors
A common beginner trap: fetch() only rejects (triggers
catch) on a genuine network failure. A 404 or 500 error from the server
still counts as a "successful" fetch as far as the Promise is concerned. Always check
response.ok or response.status yourself,
or you will silently try to parse an error page as if it were valid data.
Sending Data With POST Requests
So far we have only requested data. To send data, a new comment, a signup form, pass a second argument to
fetch() configuring the request.
async function createPost(title, body) {
const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ title, body }),
});
const newPost = await response.json();
console.log('Created:', newPost);
}
createPost('My First Post', 'Learning fetch is easier than I expected.');
Three things changed from a simple GET request: the method is set to
'POST', a Content-Type header tells the
server what format the data is in, and JSON.stringify() converts your
JavaScript object into a JSON string, since network requests can only send text, not live JavaScript
objects.
Putting It Into Practice
You now know how to request data, handle it safely, display it in the DOM, and send data back to a server, the core loop behind nearly every dynamic feature on the modern web, from search boxes to comment sections to dashboards. From here, the best next step is not another tutorial, it is building something small of your own, a weather widget, a quote generator, a simple search tool, using a free public API.
If you want a structured path that takes you from these fundamentals through full projects with guided feedback, that is exactly what the CodeVent Digital curriculum is built for.
Ready to go beyond one tutorial?
This lesson is one piece of the full CodeVent Digital frontend curriculum. Start free and build real projects step by step.
Start Learning Free