APIs & Fetch API in JavaScript – Getting Data From the Web
Over the past few days, I've learned how JavaScript works with objects, arrays, JSON, and browser storage.
Today, I explored APIs and the Fetch API.
Until now, most of the data I worked with was written directly inside my JavaScript code.
APIs change that.
They allow my application to communicate with external servers and retrieve real-world data such as users, products, weather information, posts, and much more.
Let's dive in.
What is an API?
API stands for Application Programming Interface.
An API provides a way for different software systems to communicate with each other.
For example:
Frontend
↓
API Request
↓
Backend / Server
↓
API Response
↓
Frontend
The frontend doesn't need to know how the server internally works.
It simply sends a request and receives a response.
What is an HTTP Request?
When a browser communicates with a server, it commonly uses HTTP.
Some common HTTP methods are:
| Method | Common Purpose |
|---|---|
GET |
Retrieve data |
POST |
Create/send data |
PUT |
Replace data |
PATCH |
Update part of data |
DELETE |
Remove data |
Today, I'll mainly focus on GET requests.
What is Fetch API?
The Fetch API provides a modern way to make HTTP requests from JavaScript.
Basic example:
fetch("https://example.com")
fetch() returns a Promise because the response doesn't arrive immediately.
This leads us into asynchronous JavaScript.
Understanding Asynchronous JavaScript
Some operations take time to complete.
For example:
Fetching data from a server
Reading files
Waiting for a timer
Communicating with a database
JavaScript doesn't need to stop everything while waiting.
Instead, asynchronous operations allow other code to continue running.
Example:
console.log("Start");
setTimeout(() => {
console.log("Finished");
}, 2000);
console.log("End");
Output:
Start
End
Finished
The timer runs asynchronously.
What is a Promise?
A Promise represents the eventual result of an asynchronous operation.
A Promise can be in one of three states:
Pending
Fulfilled
Rejected
Conceptually:
Pending
↓
Fulfilled
or
Pending
↓
Rejected
fetch() returns a Promise.
Using Fetch
A basic GET request looks like:
fetch("https://jsonplaceholder.typicode.com/users")
.then(response => {
return response.json();
})
.then(data => {
console.log(data);
});
Here we're requesting user data from an API.
Understanding response.json()
The response returned by fetch() isn't immediately the JavaScript object we want.
We use:
response.json()
to parse the JSON response.
It also returns a Promise.
That's why we use another .then().
Handling Errors
Network requests can fail.
We can handle errors using .catch().
fetch("https://jsonplaceholder.typicode.com/users")
.then(response => response.json())
.then(data => {
console.log(data);
})
.catch(error => {
console.error("Something went wrong:", error);
});
This prevents errors from being silently ignored.
Checking HTTP Response Status
A failed HTTP request doesn't always cause fetch() itself to reject.
For example, a server might respond with a 404 status.
We can check:
fetch("https://jsonplaceholder.typicode.com/users")
.then(response => {
if (!response.ok) {
throw new Error("Request failed");
}
return response.json();
})
.then(data => {
console.log(data);
})
.catch(error => {
console.error(error);
});
The response.ok property helps us check whether the HTTP response was successful.
Async & Await
Modern JavaScript provides a cleaner way to work with Promises using:
asyncawait
Example:
async function getUsers() {
const response = await fetch(
"https://jsonplaceholder.typicode.com/users"
);
const data = await response.json();
console.log(data);
}
getUsers();
await pauses execution inside the async function until the Promise settles.
It makes asynchronous code easier to read.
Handling Errors With try...catch
When using async/await, we can handle errors using try...catch.
async function getUsers() {
try {
const response = await fetch(
"https://jsonplaceholder.typicode.com/users"
);
if (!response.ok) {
throw new Error("Failed to fetch users");
}
const data = await response.json();
console.log(data);
} catch (error) {
console.error(error);
}
}
getUsers();
This is a common pattern in modern JavaScript applications.
Fetching and Displaying Data
The real power of APIs comes when we combine them with the DOM.
HTML:
<ul id="users"></ul>
JavaScript:
const usersList = document.querySelector("#users");
async function getUsers() {
const response = await fetch(
"https://jsonplaceholder.typicode.com/users"
);
const users = await response.json();
users.forEach(user => {
const li = document.createElement("li");
li.textContent = user.name;
usersList.appendChild(li);
});
}
getUsers();
Now the API data is dynamically displayed on the webpage.
This is where everything I've learned so far starts coming together:
API → JSON → JavaScript → DOM
API Response Flow
The complete process looks like:
User
↓
Frontend
↓
fetch()
↓
HTTP Request
↓
Server / API
↓
JSON Response
↓
JavaScript
↓
DOM
↓
Webpage
Understanding this flow is fundamental to modern web development.
Best Practices
✔ Always handle possible request failures.
✔ Check response.ok before processing the response.
✔ Use async/await when it makes asynchronous code easier to understand.
✔ Don't assume external API data will always have the expected structure.
✔ Keep API logic separate from DOM manipulation when possible.
✔ Never expose private API keys or secrets in frontend JavaScript.
My Biggest Takeaway
Today was a major step in my JavaScript journey.
Until now, I was mainly working with data that already existed inside my application.
With APIs and the Fetch API, I can now communicate with external servers and retrieve real data.
The most important flow I learned today is:
Request → Server → Response → JSON → JavaScript → DOM
This connects many of the concepts I've learned over the last few days and brings them much closer to real-world web development.
