Learn how to build a Space Explorer web app using HTML, CSS, and JavaScript with the NASA API.
Table of Contents
In this blog, we'll guide you through creating a simple yet exciting Space Explorer web app using HTML, CSS, and JavaScript. This app will fetch data from NASA's API and display it to users in an engaging way. You’ll learn how to work with the NASA API to display space images, Mars rover photos, and other space-related content.
Before starting, ensure you have a basic understanding of HTML, CSS, and JavaScript. You should also have a basic understanding of how to fetch data from APIs. This project is perfect for beginners and will help you enhance your skills while working with real-world APIs.
Prerequisites
- Basic knowledge of HTML, CSS, and JavaScript
- A text editor (e.g., Visual Studio Code)
- A browser for testing
- A NASA API key (you can get it for free from NASA's official API website)
Source Code
Step 1 (HTML Code):
Open the index.html
file and set up the basic structure of your web page. Here’s a breakdown of each part of the code:
1. HTML Document Structure
<!DOCTYPE html>
<html lang="en">
- This is the declaration for the HTML document. It specifies that the document uses HTML5 (
<!DOCTYPE html>
), and the language is set to English (lang="en"
).
2. Head Section
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>NASA Space Explorer</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap">
<link rel="stylesheet" href="styles.css">
</head>
<meta charset="UTF-8">
: Specifies the character encoding for the document, ensuring that all characters are properly displayed.<meta name="viewport" content="width=device-width, initial-scale=1.0">
: Ensures the page is responsive by adjusting the layout for different screen sizes (important for mobile devices).<title>NASA Space Explorer</title>
: Sets the title of the page, which will appear in the browser tab.<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap">
: Links to an external Google font, "Poppins," which will be used for styling text on the page.<link rel="stylesheet" href="styles.css">
: Links to an external CSS file (styles.css
) for additional styling of the page.
3. Body Section
<body>
<header>
<h1>NASA Space Explorer</h1>
<qp>Discover the universe through NASA's data</p>
</header>
<header>
: This section contains the title and description of the website.<h1>
: Displays the main heading of the website: "NASA Space Explorer."<p>
: Displays a brief description of the site: "Discover the universe through NASA's data."
4. Layout Section
<div class="layout">
<aside class="sidebar">
<ul>
<li><button onclick="loadContent('apod', this)">Astronomy Picture of the Day</button></li>
<li><button onclick="loadContent('mars', this)">Mars Rover Photos</button></li>
<li><button onclick="loadContent('neo', this)">Near Earth Objects</button></li>
<li><button onclick="loadContent('search', this)">Search Images</button></li>
</ul>
</aside>
<main class="content" id="mainContent">
<h2>Welcome to NASA Space Explorer</h2>
<p>Click on the tabs to explore NASA's data.</p>
</main>
</div>
<div class="layout">
: A container that holds the sidebar and main content area.<aside class="sidebar">
: This is the sidebar section of the page. It contains a list of buttons that allow users to navigate and load different types of NASA data.<ul>
: Unordered list containing the buttons for different sections.<button onclick="loadContent('apod', this)">
: Each button triggers a JavaScript functionloadContent()
when clicked, passing a string (like 'apod') and the button itself as arguments.
<main class="content" id="mainContent">
: The main content area where the content will be displayed based on the button clicked in the sidebar.<h2>
: A welcome message for the users.<p>
: A brief instruction for the users to click on the tabs to explore the data.
5. Modal Section
<!-- Modal -->
<div class="modal" id="detailsModal">
<span class="modal-close" onclick="closeModal()">×</span>
<div class="modal-content" id="modalContent"></div>
</div>
<div class="modal" id="detailsModal">
: This is a modal that will be used to display detailed information when triggered. It is initially hidden.<span class="modal-close" onclick="closeModal()">×</span>
: A close button (×
represents an "X" symbol) that, when clicked, will call thecloseModal()
JavaScript function to close the modal.<div class="modal-content" id="modalContent"></div>
: This is the container where the modal's content will be dynamically inserted via JavaScript.
6. Script Section
<script src="script.js"></script>
</body>
</html>
<script src="script.js"></script>
: This links to an external JavaScript file (script.js
), which will handle the functionality of loading content when a button is clicked, opening and closing the modal, and other interactive features.
Step 2 (CSS Code):
Now, let’s style the page in style.css
to make it visually appealing. Here’s a breakdown of each part of the CSS code:
Global Styling (body
)
- General layout: The
body
is set to use a flexbox layout (display: flex
), with a column direction, ensuring the content stretches vertically (min-height: 100vh
). - Styling: Uses a light background (
background-color: #f0f4f8
) and a readable text color (#333
). - Transitions: Smooth transition effect for background color changes (
transition: background-color 0.3s ease
).
Header Styling (header
)
- Positioning: The header has a sticky position (
position: sticky; top: 0;
) so it remains visible at the top of the page while scrolling. - Aesthetics: The background color is a purple shade (
#825cff
), and text is white for contrast. It has padding for spacing and a box shadow for a subtle floating effect.
Layout Styling (.layout
)
- Flexbox: The layout is structured as a flex container with a gap between the sidebar and content section.
- Padding: Added padding for spacing inside the layout, with specific measurements for the sidebar and content areas.
Sidebar Styling (.sidebar
)
- Sticky Sidebar: The sidebar stays fixed within the viewport as you scroll (
position: sticky; top: 5rem;
), giving it a fixed height with acalc
function. - Appearance: White background with subtle shadow and rounded corners (
border-radius: 10px
).
Button Styling within Sidebar (.sidebar button
)
- Text Button: The buttons in the sidebar are styled as bold, left-aligned, and interactive. On hover, they change color (
color: #825cff
), thanks to the transition effect.
Content Section (.content
)
- Design: The content area has padding, a white background, and rounded corners with a soft shadow to make it stand out.
Grid and Card Layout (.grid
, .card
)
- Grid Layout: Items inside the content section are arranged in a responsive grid layout using CSS Grid (
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
). - Card Styling: Each card inside the grid has a border, shadow, and hover effects like transforming (
transform: translateY(-5px)
) and shadow enhancement.
Search Section (.search-container
)
- Flexbox Search Box: The search input and button are aligned using flexbox. The input changes its border color on focus (
border-color: #825CFF
), and the button has a color transition on hover.
Modal Styling (.modal
, .modal-content
)
- Fixed Modal: The modal is positioned fixed (
position: fixed
), covering the entire screen. It has a semi-transparent black background (background: rgba(0, 0, 0, 0.8)
). - Centered Content: The modal's content is centered and has rounded corners, with shadow and padding for aesthetics.
- Close Button: The modal close button is styled to appear at the top-right, changing color on hover.
Additional Features
- Image Styling: The images in cards and modals are responsive, set to cover the container (
object-fit: cover
) while maintaining good visual proportions. - Hover Effects: Several elements like the cards and buttons have smooth transitions on hover, improving the user interaction feel.
body {
margin: 0;
font-family: 'Poppins', sans-serif;
background-color: #f0f4f8;
color: #333;
display: flex;
flex-direction: column;
min-height: 100vh;
transition: background-color 0.3s ease;
}
header {
background-color: #825cff;
color: white;
padding: 1rem 2rem;
text-align: center;
position: sticky;
top: 0;
z-index: 1000;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
}
header h1 {
margin: 0;
font-size: 2.5rem;
font-weight: 600;
}
header p {
margin: 0.5rem 0 0;
font-size: 1rem;
font-weight: 400;
}
.layout {
display: flex;
flex: 1;
padding: 2rem;
gap: 2rem;
}
.sidebar {
width: 250px;
background-color: #ffffff;
box-shadow: 2px 0 5px rgba(0, 0, 0, 0.1);
padding: 1.5rem;
position: sticky;
top: 5rem;
height: calc(100vh - 5rem);
border-radius: 10px;
}
.sidebar ul {
list-style: none;
padding: 0;
}
.sidebar li {
margin: 1rem 0;
}
.sidebar button {
font-family: 'Poppins', sans-serif;
width: 100%;
background: none;
border: none;
text-align: left;
font-size: 1rem;
padding: 0.75rem 0;
color: #333;
cursor: pointer;
font-weight: bold;
outline: none;
transition: color 0.3s ease;
}
.sidebar button:hover, button.active {
color: #825cff;
}
.content {
flex: 1;
padding: 2rem;
background-color: #fff;
border-radius: 10px;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 1.5rem;
margin-top: 20px;
}
.card {
width: 100%;
border: 1px solid #ccc;
border-radius: 10px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
overflow: hidden;
text-align: center;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.card:hover {
transform: translateY(-5px);
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
}
.card img {
width: 100%;
height: 200px;
object-fit: cover;
cursor: pointer;
transition: opacity 0.3s ease;
}
.card img:hover {
opacity: 0.8;
}
.card-content {
padding: 1rem;
}
.card-title {
font-size: 1.2rem;
margin: 0;
color: #825cff;
font-weight: 600;
}
.details-button {
display: inline-block;
margin-top: 1rem;
color: #825cff;
text-decoration: none;
font-weight: bold;
cursor: pointer;
transition: color 0.3s ease;
}
.details-button:hover {
text-decoration: underline;
color: #5b42b3;
}
.search-container {
display: flex;
justify-content: center;
align-items: center;
margin: 20px 0;
gap: 10px;
}
.search-input {
font-family: 'Poppins', sans-serif;
width: 300px;
padding: 12px;
font-size: 16px;
border: 1px solid #ccc;
border-radius: 5px;
outline: none;
transition: border-color 0.3s ease;
}
.search-input:focus {
border-color: #825CFF;
}
.search-button {
font-family: 'Poppins', sans-serif;
padding: 12px 20px;
font-size: 16px;
background-color: #825CFF;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.3s ease;
}
.search-button:hover {
background-color: #6a4bff;
}
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
justify-content: center;
align-items: center;
z-index: 1000;
color: white;
padding: 1rem;
transition: opacity 0.3s ease;
}
.modal-content {
background: white;
color: #333;
max-width: 500px;
width: 90%;
border-radius: 10px;
padding: 1rem;
text-align: center;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
overflow: hidden;
}
.modal img {
width: 100%;
height: auto;
max-height: 300px;
object-fit: cover;
border-radius: 10px;
}
.modal h2 {
color: #825cff;
font-size: 1.5rem;
font-weight: 600;
}
.modal p {
margin: 1rem 0;
font-size: 1rem;
}
.modal a {
color: #825cff;
text-decoration: none;
font-weight: bold;
}
.modal a:hover {
text-decoration: underline;
}
.modal-close {
position: absolute;
top: 20px;
right: 50px;
color: white;
font-size: 2rem;
cursor: pointer;
transition: color 0.3s ease;
}
.modal-close:hover {
color: #825cff;
}
Step 3 (JavaScript Code):
Finally, we need to create a function in JavaScript. This JavaScript code interacts with NASA's public APIs to fetch data about space-related topics like Astronomy Picture of the Day (APOD), Mars rover photos, Near-Earth Objects (NEOs), and NASA images based on search terms. Here's a breakdown of the code:
Key Functions:
loadContent(tab, button)
:- This function is called to load content based on the selected tab (like 'apod', 'mars', 'neo', or 'search').
- It updates the
mainContent
div to show a loading message, then fetches data from the relevant API depending on thetab
parameter. - It checks for API rate limiting (status 429) and displays an error message if too many requests are made.
- After fetching data, it updates
mainContent
with the relevant content (e.g., images, explanations, or a search interface).
updateActiveButton(button)
:- This function updates the active state of buttons in a sidebar. It removes the
active
class from all buttons and adds it to the currently clicked button.
- This function updates the active state of buttons in a sidebar. It removes the
searchImages()
:- This function allows users to search for NASA images using the
searchInput
value. - It fetches data from the NASA image search API and displays the results in a grid.
- If no results are found, it shows a message indicating that no results were found for the search term.
- This function allows users to search for NASA images using the
generateGridMars(items, alt)
:- This function generates a grid of Mars rover photos (from the Curiosity rover) and displays them as cards with images and descriptions.
- Each image can be clicked to open a modal with more details.
generateGrid(items, alt)
:- This function generates a grid of NASA images (from the image search API).
- It displays images in a grid format, each with a title and description. Clicking an image opens a modal with more details.
openModal(title, details, image)
:- This function opens a modal to display detailed information about an image.
- The modal shows the image, title, and description passed as arguments.
closeModal()
:- This function closes the modal when called.
Key Features:
- Error Handling: If there are too many requests to the NASA API (status 429), it shows a message saying "You have made too many requests. Please try again later."
- Dynamic Content Loading: Based on the selected tab, it fetches and displays different content like APOD, Mars photos, NEOs, or search results.
- Image Modal: Clicking on an image opens a modal showing a larger version of the image along with more details.
- Search Functionality: Users can search for NASA images and view the results in a grid.
Example Flow:
- When the user clicks on a tab like "APOD", the
loadContent
function fetches the Astronomy Picture of the Day data and displays it. - If the user enters a search term in the "Search" tab, the
searchImages
function fetches and displays relevant NASA images. - Clicking on any image opens a modal with more details about that image.
API Usage:
- APOD API: Fetches the Astronomy Picture of the Day.
- Mars Rover API: Fetches photos from the Curiosity rover on Mars.
- NEO API: Fetches information about Near-Earth Objects.
- NASA Image Search API: Allows users to search for NASA images based on a query.
const API_KEY = 'DEMO_KEY';
async function loadContent(tab, button) {
const mainContent = document.getElementById('mainContent');
mainContent.innerHTML = '<p>Loading...</p>';
updateActiveButton(button);
try {
if (tab === 'apod') {
const response = await fetch(`https://api.nasa.gov/planetary/apod?api_key=${API_KEY}`);
if (response.status === 429) {
mainContent.innerHTML = '<p>You have made too many requests. Please try again later.</p>';
return;
}
const data = await response.json();
mainContent.innerHTML = `
<h2>${data.title}</h2>
<img src="${data.url}" alt="${data.title}">
<p>${data.explanation}</p>`;
} else if (tab === 'mars') {
const response = await fetch(`https://api.nasa.gov/mars-photos/api/v1/rovers/curiosity/photos?sol=1000&api_key=${API_KEY}`);
if (response.status === 429) {
mainContent.innerHTML = '<p>You have made too many requests. Please try again later.</p>';
return;
}
const data = await response.json();
mainContent.innerHTML = `<h2>Mars Rover Photos</h2>`;
mainContent.innerHTML += generateGridMars(data.photos, 'Mars Rover Photo');
} else if (tab === 'neo') {
const response = await fetch(`https://api.nasa.gov/neo/rest/v1/feed?api_key=${API_KEY}`);
if (response.status === 429) {
mainContent.innerHTML = '<p>You have made too many requests. Please try again later.</p>';
return;
}
const data = await response.json();
mainContent.innerHTML = `<h2>Near Earth Objects</h2>`;
data.near_earth_objects[Object.keys(data.near_earth_objects)[0]].forEach(neo => {
const closeApproach = neo.close_approach_data[0];
const closeDate = closeApproach ? closeApproach.close_approach_date : 'N/A';
const distance = closeApproach ? closeApproach.miss_distance.kilometers : 'N/A';
mainContent.innerHTML += `
<div class="card">
<div class="card-content">
<h2 class="card-title">${neo.name}</h2>
<p><strong>Estimated Diameter:</strong> ${neo.estimated_diameter.kilometers.estimated_diameter_min.toFixed(2)} - ${neo.estimated_diameter.kilometers.estimated_diameter_max.toFixed(2)} km</p>
<p><strong>Potentially Hazardous:</strong> ${neo.is_potentially_hazardous_asteroid ? 'Yes' : 'No'}</p>
<p><strong>Close Approach Date:</strong> ${closeDate}</p>
<p><strong>Distance from Earth:</strong> ${parseFloat(distance).toFixed(2)} km</p>
</div>
</div>`;
});
} else if (tab === 'search') {
mainContent.innerHTML = `
<h2 style="text-align:center;">Search for NASA Images</h2>
<div class="search-container">
<input type="search" id="searchInput" placeholder="Enter search term" class="search-input">
<button onclick="searchImages()" class="search-button">Search</button>
</div>
<div id="searchResults" class="search-results"></div>`;
}
} catch (error) {
mainContent.innerHTML = '<p>Error loading data. Please try again later.</p>';
console.error('Error fetching data:', error);
}
}
function updateActiveButton(button) {
const buttons = document.querySelectorAll('aside.sidebar button');
buttons.forEach(btn => btn.classList.remove('active'));
button.classList.add('active');
}
async function searchImages() {
const query = document.getElementById('searchInput').value;
const searchResults = document.getElementById('searchResults');
if (query) {
searchResults.innerHTML = '<p>Searching...</p>';
try {
const response = await fetch(`https://images-api.nasa.gov/search?q=${query}`);
const data = await response.json();
if (data.collection.items.length > 0) {
searchResults.innerHTML = generateGrid(data.collection.items, 'NASA Image');
} else {
searchResults.innerHTML = `<p>No results found for "${query}".</p>`;
}
} catch (error) {
searchResults.innerHTML = '<p>Error searching for images. Please try again later.</p>';
console.error('Error fetching search results:', error);
}
} else {
searchResults.innerHTML = '<p>Please enter a search term.</p>';
}
}
function generateGridMars(items, alt) {
let grid = '<div class="grid">';
items.forEach(item => {
grid += `
<div class="card">
<img src="${item.img_src || 'https://www.codewithfaraz.com/tools/placeholder?size=300'}" alt="${alt}" onclick="openModal('${alt}', 'Taken by: ${item.rover.name}', '${item.img_src}')">
<div class="card-content">
<h2 class="card-title">${alt}</h2>
</div>
</div>`;
});
grid += '</div>';
return grid;
}
function generateGrid(items, alt) {
let grid = '<div class="grid">';
items.forEach(item => {
const imageUrl = item.links ? item.links[0].href : 'https://www.codewithfaraz.com/tools/placeholder?size=300';
const description = item.data[0].description || '';
grid += `
<div class="card">
<img src="${imageUrl}" alt="${alt}" onclick="openModal('Date Created: ${item.data[0].date_created}', '${description.replace(/'/g, "\\'")}', '${imageUrl}')">
<div class="card-content">
<h2 class="card-title">${item.data[0].title}</h2>
</div>
</div>`;
});
grid += '</div>';
return grid;
}
function openModal(title, details, image) {
const modalContent = document.getElementById('modalContent');
modalContent.innerHTML = `
<img src="${image}" alt="${title}">
<h2>${title}</h2>
<p>${details}</p>
`;
document.getElementById('detailsModal').style.display = 'flex';
}
function closeModal() {
document.getElementById('detailsModal').style.display = 'none';
}
Final Output:
Conclusion:
In this project, you've successfully built a Space Explorer web app using HTML, CSS, and JavaScript with the NASA API. By integrating real-time space data such as the Astronomy Picture of the Day and Mars rover photos, you’ve created an interactive and engaging user experience.
This app serves as a great starting point for learning how to work with APIs and dynamic content in web development. As you continue to explore new features and improve the app, you'll enhance your skills in front-end development and JavaScript.
Feel free to expand this project by adding more space-related data from the NASA API or by improving the design and functionality. The possibilities are endless, and this app is a solid foundation for building more complex and exciting web applications.
That’s a wrap!
I hope you enjoyed this post. Now, with these examples, you can create your own amazing page.
Did you like it? Let me know in the comments below 🔥 and you can support me by buying me a coffee
And don’t forget to sign up to our email newsletter so you can get useful content like this sent right to your inbox!
Thanks!
Faraz 😊