Working with Web APIs
Learn how to interact with external data sources using the Fetch API and handle responses like a pro.
The modern web is built on connections between applications. Web APIs (Application Programming Interfaces) are the bridges that allow different systems to communicate with each other, enabling you to fetch data from servers, send data to databases, and integrate third-party services into your applications.
What are Web APIs?
A Web API is a set of rules and protocols that allow different software applications to communicate with each other. APIs define the methods and data formats that applications can use to request and exchange information.
There are many types of APIs you’ll encounter:
- REST APIs - The most common type, using HTTP requests to GET, POST, PUT, DELETE data
- GraphQL APIs - A more flexible alternative to REST that allows clients to request exactly the data they need
- WebSocket APIs - Enable two-way communication channels over a single TCP connection
- Browser APIs - Built into your browser (like Geolocation, Web Storage, Canvas)
The Fetch API
The Fetch API is a modern interface for making HTTP requests in JavaScript. It’s more powerful and flexible than the older XMLHttpRequest, and it uses Promises to handle asynchronous operations.
Basic Fetch Request
Here’s a simple GET request:
fetch("https://api.example.com/data")
.then((response) => {
// Check if the request was successful
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json(); // Parse the JSON in the response
})
.then((data) => {
// Work with the data
console.log(data);
})
.catch((error) => {
// Handle errors
console.error("Fetch error:", error);
});
Using Fetch with Async/Await
For cleaner code, you can use async/await with Fetch:
async function fetchData() {
try {
const response = await fetch("https://api.example.com/data");
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const data = await response.json();
console.log(data);
return data;
} catch (error) {
console.error("Fetch error:", error);
}
}
// Call the function
fetchData();
Handling Different Response Types
The Fetch API can handle various response formats:
// JSON (most common)
const jsonData = await response.json();
// Plain text
const textData = await response.text();
// Form data
const formData = await response.formData();
// Binary data as Blob
const blobData = await response.blob();
// Array buffer for binary data
const arrayBufferData = await response.arrayBuffer();
Making Different Types of Requests
POST Request (Sending Data)
async function postData(url, data) {
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data), // Convert JavaScript object to JSON string
});
return response.json();
}
// Example usage
const newUser = {
name: "Jane Doe",
email: "jane@example.com",
};
postData("https://api.example.com/users", newUser)
.then((data) => console.log("Success:", data))
.catch((error) => console.error("Error:", error));
PUT Request (Updating Data)
async function updateUser(id, userData) {
const response = await fetch(`https://api.example.com/users/${id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(userData),
});
return response.json();
}
DELETE Request
async function deleteUser(id) {
const response = await fetch(`https://api.example.com/users/${id}`, {
method: "DELETE",
});
// Some APIs might return no content on DELETE
if (response.status === 204) {
return { success: true };
}
return response.json();
}
Working with Headers
Headers allow you to send additional information with your requests:
const response = await fetch("https://api.example.com/protected-data", {
headers: {
Authorization: "Bearer YOUR_ACCESS_TOKEN",
"Content-Type": "application/json",
Accept: "application/json",
"X-Custom-Header": "CustomValue",
},
});
You can also read headers from responses:
const response = await fetch("https://api.example.com/data");
// Single header
const contentType = response.headers.get("Content-Type");
// Iterate through all headers
for (const [key, value] of response.headers.entries()) {
console.log(`${key}: ${value}`);
}
Handling Timeouts and Cancellation
The Fetch API doesn’t directly support timeouts, but you can implement them:
async function fetchWithTimeout(url, options = {}, timeout = 5000) {
// Create abort controller for cancelling the fetch
const controller = new AbortController();
const { signal } = controller;
// Create the promise that rejects after the timeout
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => {
controller.abort();
reject(new Error(`Request timed out after ${timeout}ms`));
}, timeout);
});
// Race the fetch against the timeout
return Promise.race([fetch(url, { ...options, signal }), timeoutPromise]);
}
// Usage
try {
const response = await fetchWithTimeout(
"https://api.example.com/data",
{},
3000
);
const data = await response.json();
console.log(data);
} catch (error) {
console.error("Error:", error.message);
}
Practical Example: Building a Weather App
Let’s put everything together by creating a simple weather app using a public API:
async function getWeather(city) {
const apiKey = "your_api_key_here"; // You would need to get an API key
const url = `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${apiKey}&units=metric`;
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Weather API error: ${response.status}`);
}
const data = await response.json();
// Extract the information we need
const weather = {
city: data.name,
country: data.sys.country,
temp: data.main.temp,
description: data.weather[0].description,
icon: data.weather[0].icon,
humidity: data.main.humidity,
windSpeed: data.wind.speed,
};
return weather;
} catch (error) {
console.error("Failed to fetch weather:", error);
throw error;
}
}
// UI code to display the weather
function displayWeather(weather) {
const weatherDiv = document.getElementById("weather");
weatherDiv.innerHTML = `
<h2>${weather.city}, ${weather.country}</h2>
<div class="temp">${Math.round(weather.temp)}°C</div>
<div class="description">${weather.description}</div>
<img src="https://openweathermap.org/img/w/${weather.icon}.png" alt="${
weather.description
}">
<div class="details">
<div>Humidity: ${weather.humidity}%</div>
<div>Wind: ${weather.windSpeed} m/s</div>
</div>
`;
}
// Event handler for the form
document
.getElementById("weather-form")
.addEventListener("submit", async (e) => {
e.preventDefault();
const city = document.getElementById("city-input").value;
try {
const weatherData = await getWeather(city);
displayWeather(weatherData);
} catch (error) {
document.getElementById("weather").innerHTML = `
<div class="error">Failed to fetch weather data. Please try again.</div>
`;
}
});
|GET /users| B[Server]
A -->|POST /users| B
A -->|GET /users/123| B
A -->|PUT /users/123| B
A -->|DELETE /users/123| B
B -->|Response| A
—>
Common HTTP Status Codes
When working with APIs, you’ll encounter these status codes:
- 200 OK: Success
- 201 Created: Resource created successfully
- 204 No Content: Success but no content to return
- 400 Bad Request: Invalid input
- 401 Unauthorized: Authentication required
- 403 Forbidden: Not allowed to access this resource
- 404 Not Found: Resource doesn’t exist
- 500 Internal Server Error: Server error
Error Handling Best Practices
Robust error handling is critical when working with APIs:
async function fetchAPI(url) {
try {
const response = await fetch(url);
if (response.status === 404) {
throw new Error("Resource not found");
}
if (response.status === 401) {
// Maybe trigger a new login
throw new Error("Authentication required");
}
if (!response.ok) {
// Try to get error details from response
try {
const errorData = await response.json();
throw new Error(errorData.message || `API error: ${response.status}`);
} catch (jsonError) {
// If parsing JSON fails, just use the status
throw new Error(`API error: ${response.status}`);
}
}
return response.json();
} catch (error) {
// Log the error for debugging
console.error("API request failed:", error);
// Provide user-friendly message
if (error.message.includes("Failed to fetch")) {
throw new Error("Network error. Please check your internet connection.");
}
// Re-throw the error for the caller to handle
throw error;
}
}
Rate Limiting and Throttling Requests
Many APIs limit how many requests you can make in a given time period. Here’s a simple way to throttle requests:
class APIThrottler {
constructor(requestsPerMinute) {
this.requestsPerMinute = requestsPerMinute;
this.queue = [];
this.processing = false;
}
async fetch(url, options = {}) {
return new Promise((resolve, reject) => {
this.queue.push({
url,
options,
resolve,
reject,
});
if (!this.processing) {
this.processQueue();
}
});
}
async processQueue() {
this.processing = true;
while (this.queue.length > 0) {
const { url, options, resolve, reject } = this.queue.shift();
try {
const response = await fetch(url, options);
const data = await response.json();
resolve(data);
} catch (error) {
reject(error);
}
// Wait to respect rate limit
await new Promise((r) =>
setTimeout(r, (60 * 1000) / this.requestsPerMinute)
);
}
this.processing = false;
}
}
// Usage
const apiClient = new APIThrottler(30); // 30 requests per minute
async function getMultipleUsers() {
const userIds = [1, 2, 3, 4, 5];
const promises = userIds.map((id) =>
apiClient.fetch(`https://api.example.com/users/${id}`)
);
return Promise.all(promises);
}
Conclusion
Web APIs are the backbone of modern web development, connecting applications and enabling rich, data-driven experiences. The Fetch API provides a powerful way to interact with these services from your JavaScript code.
As you work with APIs, remember these key points:
- Always handle errors gracefully
- Use async/await for cleaner, more readable code
- Respect API rate limits and implement proper throttling
- Validate and sanitize data both before sending and after receiving
- Keep sensitive information like API keys secure
With these skills, you’ll be able to integrate any third-party service or build your own API-powered applications with confidence.