Practice and reinforce the concepts from Lesson 3
By completing this activity, you will:
Pick one API service that interests you most:
Best for: Comic book fans
Best for: Space enthusiasts
Best for: GIF lovers and meme creators
Best for: Movie and TV show enthusiasts
Access the template here: Multi-API Dashboard Template
git clone
and navigate to the template folderindex.html
in your browser to start!The template includes:
Make your first authenticated request with your chosen API.
const SUPERHERO_API_KEY = 'YOUR_API_KEY_HERE';
async function getSuperheroByName(heroName) {
const url = `https://superheroapi.com/api/${SUPERHERO_API_KEY}/search/${heroName}`;
try {
const response = await fetch(url);
const data = await response.json();
console.log('Superhero data:', data);
if (data.response === 'success') {
const hero = data.results[0];
document.getElementById('result').innerHTML = `
<h3>${hero.name}</h3>
<img src="${hero.image.url}" alt="${hero.name}" style="max-width: 300px;">
<p><strong>Full Name:</strong> ${hero.biography['full-name']}</p>
<p><strong>Intelligence:</strong> ${hero.powerstats.intelligence}/100</p>
<p><strong>Strength:</strong> ${hero.powerstats.strength}/100</p>
<p><strong>Speed:</strong> ${hero.powerstats.speed}/100</p>
`;
} else {
document.getElementById('result').innerHTML = '<p>Hero not found!</p>';
}
} catch (error) {
console.error('Error fetching superhero:', error);
}
}
const NASA_API_KEY = 'YOUR_API_KEY_HERE';
async function getNASAImageOfDay() {
const url = `https://api.nasa.gov/planetary/apod?api_key=${NASA_API_KEY}`;
try {
const response = await fetch(url);
const data = await response.json();
console.log('NASA data:', data);
document.getElementById('result').innerHTML = `
<h3>${data.title}</h3>
<img src="${data.url}" alt="${data.title}" style="max-width: 400px;">
<p>${data.explanation.substring(0, 200)}...</p>
<p><strong>Date:</strong> ${data.date}</p>
`;
} catch (error) {
console.error('Error fetching NASA data:', error);
}
}
const GIPHY_API_KEY = 'YOUR_API_KEY_HERE';
async function searchGifs(searchTerm) {
const url = `https://api.giphy.com/v1/gifs/search?api_key=${GIPHY_API_KEY}&q=${searchTerm}&limit=6&rating=g`;
try {
const response = await fetch(url);
const data = await response.json();
console.log('GIPHY data:', data);
let html = `<h3>GIFs for "${searchTerm}"</h3><div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px;">`;
data.data.forEach(gif => {
html += `
<div>
<img src="${gif.images.fixed_height.url}" alt="${gif.title}" style="width: 100%;">
</div>
`;
});
html += '</div>';
document.getElementById('result').innerHTML = html;
} catch (error) {
console.error('Error fetching GIFs:', error);
}
}
const TMDB_API_KEY = 'YOUR_API_KEY_HERE';
async function searchMovies(movieTitle) {
const url = `https://api.themoviedb.org/3/search/movie?api_key=${TMDB_API_KEY}&query=${movieTitle}`;
try {
const response = await fetch(url);
const data = await response.json();
console.log('Movie data:', data);
if (data.results.length > 0) {
const movie = data.results[0];
const posterUrl = movie.poster_path ? `https://image.tmdb.org/t/p/w300${movie.poster_path}` : 'https://via.placeholder.com/300x450?text=No+Poster';
document.getElementById('result').innerHTML = `
<h3>${movie.title}</h3>
<img src="${posterUrl}" alt="${movie.title}" style="max-width: 250px;">
<p><strong>Release Date:</strong> ${movie.release_date}</p>
<p><strong>Rating:</strong> ${movie.vote_average}/10</p>
<p>${movie.overview.substring(0, 200)}...</p>
`;
} else {
document.getElementById('result').innerHTML = '<p>Movie not found!</p>';
}
} catch (error) {
console.error('Error fetching movie:', error);
}
}
Create a user-friendly search interface for your chosen API.
document.getElementById('searchForm').addEventListener('submit', async (e) => {
e.preventDefault();
const heroInput = document.getElementById('searchInput');
const heroName = heroInput.value.trim();
if (heroName) {
try {
document.getElementById('result').innerHTML = '<p>Searching for superhero...</p>';
await getSuperheroByName(heroName);
heroInput.value = ''; // Clear form
} catch (error) {
document.getElementById('result').innerHTML = '<p style="color: red;">Hero not found. Try searching for Batman, Superman, or Spider-Man!</p>';
}
}
});
document.getElementById('gifForm').addEventListener('submit', async (e) => {
e.preventDefault();
const searchInput = document.getElementById('gifSearchInput');
const searchTerm = searchInput.value.trim();
if (searchTerm) {
try {
document.getElementById('result').innerHTML = '<p>Finding awesome GIFs...</p>';
await searchGifs(searchTerm);
searchInput.value = ''; // Clear form
} catch (error) {
document.getElementById('result').innerHTML = '<p style="color: red;">No GIFs found. Try searching for cats, dogs, or funny!</p>';
}
}
});
document.getElementById('movieForm').addEventListener('submit', async (e) => {
e.preventDefault();
const movieInput = document.getElementById('movieSearchInput');
const movieTitle = movieInput.value.trim();
if (movieTitle) {
try {
document.getElementById('result').innerHTML = '<p>Searching for movie...</p>';
await searchMovies(movieTitle);
movieInput.value = ''; // Clear form
} catch (error) {
document.getElementById('result').innerHTML = '<p style="color: red;">Movie not found. Try searching for popular movies!</p>';
}
}
});
document.getElementById('dateForm').addEventListener('submit', async (e) => {
e.preventDefault();
const dateInput = document.getElementById('dateInput');
const selectedDate = dateInput.value;
if (selectedDate) {
await getNASAImageByDate(selectedDate);
}
});
async function getNASAImageByDate(date) {
const url = `https://api.nasa.gov/planetary/apod?api_key=${NASA_API_KEY}&date=${date}`;
try {
const response = await fetch(url);
const data = await response.json();
document.getElementById('result').innerHTML = `
<h3>${data.title}</h3>
<img src="${data.url}" alt="${data.title}" style="max-width: 400px;">
<p>${data.explanation}</p>
<p><strong>Date:</strong> ${data.date}</p>
`;
} catch (error) {
document.getElementById('result').innerHTML = '<p style="color: red;">No image available for this date.</p>';
}
}
Implement basic security practices for API key handling.
Your API key is visible in the browser! This is a problem because:
Challenge A: Basic Key Validation
function validateApiKey(key) {
// Check if key exists and meets basic criteria
if (!key) {
throw new Error('API key is required');
}
if (key === 'YOUR_API_KEY_HERE') {
throw new Error('Please replace with your actual API key');
}
// Different APIs have different key formats
if (key.length < 10) {
throw new Error('API key appears to be too short');
}
return true;
}
// Use before making requests
if (validateApiKey(API_KEY)) {
// Make your API call
}
Challenge B: Rate Limiting Protection
class APIRateLimiter {
constructor(maxRequests = 60, timeWindow = 60000) { // 60 requests per minute
this.requests = [];
this.maxRequests = maxRequests;
this.timeWindow = timeWindow;
}
canMakeRequest() {
const now = Date.now();
// Remove old requests outside time window
this.requests = this.requests.filter(time => now - time < this.timeWindow);
if (this.requests.length >= this.maxRequests) {
return false;
}
this.requests.push(now);
return true;
}
}
const rateLimiter = new APIRateLimiter();
// Use before API calls
if (!rateLimiter.canMakeRequest()) {
document.getElementById('result').innerHTML = '<p style="color: orange;">Rate limit reached. Please wait before making another request.</p>';
return;
}
Challenge C: Error Response Handling
async function makeSecureAPICall(url) {
try {
const response = await fetch(url);
// Handle different error status codes
if (response.status === 401) {
throw new Error('Invalid API key. Please check your credentials.');
}
if (response.status === 429) {
throw new Error('Rate limit exceeded. Please wait before trying again.');
}
if (response.status === 403) {
throw new Error('Access forbidden. Check your API key permissions.');
}
if (!response.ok) {
throw new Error(`API error: ${response.status} ${response.statusText}`);
}
return await response.json();
} catch (error) {
console.error('API call failed:', error);
throw error;
}
}
Add comments to your code explaining:
Try these searches to verify your application works:
For Superhero API:
For GIPHY API:
For Movie API:
For NASA API:
API Key Not Working
CORS Errors
No Data Displaying
console.log()
statementsSearch Not Working
If you finish early, try these bonus features:
Combine multiple APIs in one interface:
// Example: Superhero + Movie combination
async function createEntertainmentDashboard() {
const promises = [
getSuperheroByName('Batman'),
searchMovies('Batman'),
getNASAImageOfDay()
];
try {
await Promise.all(promises);
// Display superhero, related movie, and space image side by side
} catch (error) {
console.error('Dashboard error:', error);
}
}
Avoid unnecessary API calls:
class APICache {
constructor(ttl = 300000) { // 5 minutes default
this.cache = new Map();
this.ttl = ttl;
}
set(key, data) {
this.cache.set(key, {
data,
timestamp: Date.now()
});
}
get(key) {
const item = this.cache.get(key);
if (!item) return null;
if (Date.now() - item.timestamp > this.ttl) {
this.cache.delete(key);
return null;
}
return item.data;
}
}
In the next activity, we'll explore different types of API authentication methods and learn about handling API rate limits and error responses.
💡 Tip: Save your API key somewhere safe - we'll use these fun APIs throughout this course for various exercises! Whether you chose superheroes, movies, GIFs, or space, there are more exciting projects ahead!