NASA Astronomy Picture of the Day API

·
Cover for NASA Astronomy Picture of the Day API

NASA’s Astronomy Picture of the Day features a fantastic daily photo or video of the cosmos that you can access with your apps through NASA’s APOD API. This topic walks you through an example client-side app that calls the API to display the photo of the day on a webpage.

The APOD API is a public REST API provided by NASA. It returns a JSON object containing a daily image or video along with a description and metadata.

This APOD script is a client-side web app that is embedded in an HTML file for maximum simplicity and portability. There are no server-side dependencies needed to run the example. You can just save the example as an HTML file and open it in a web browser on your local machine.

Here’s a screenshot of the example: Screenshot of this APOD example Image credit and copyright: José J. Chambó

You can also go straight to the complete code example.

Structure

The APOD script is a JavaScript app that’s embedded in an HTML document at the end of the <body> element, so the UI elements are ready to display incoming data from the API before the script executes.

NOTE: Alternatively, for scripts in the <head> element, you can use the DOMContentLoaded event. See MDN: DOMContentLoaded for details.

The script uses this structure:

  1. UI - Displays data retrieved from the APOD API.
  2. API configuration - Defines constants that store the API settings.
  3. DOM references - Caches references to HTML elements in the UI.
  4. Helper functions - Small, single-purpose utility functions.
  5. Main application logic - Core functions that fetch data and render the UI.
  6. Entry point - Single function call to start the app.

UI

The HTML for the APOD example includes UI elements that the script populates after fetching data from the API.

Screenshot that identifies the UI elements This image identifies the elements of the web UI.

The UI displays these main sections:

  • Header — Displays the page title and a brief description.
  • Status message — Displays a loading message while fetching data.
  • Content section — Contains placeholders for APOD data (hidden until data loads).
  • Error container — Displays error messages if the API request fails (hidden by default).

The following table describes each UI element the script interacts with.

ElementIDPurpose
<p>statusDisplays loading status; hidden after content loads.
<section>apodContainer for all APOD content; uses hidden attribute until data is ready.
<h2>titleDisplays the APOD title.
<p>dateDisplays the APOD date.
<figure>mediaContains the image or video element.
<p>explanationDisplays the APOD description.
<dd>mediaTypeDisplays the media type (image or video).
<dd>hdUrlDisplays a link to the HD image.
<dd>copyrightDisplays copyright information.
<dd>serviceVersionDisplays the API service version.
<p>errorDisplays error messages; hidden by default.

Here’s the code for the UI:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <meta name="description" content="Displays NASA's Astronomy Picture of the Day using the APOD API." />
    <title>NASA APOD (Client-side Example)</title>
    <style>
      /* Minimal styling for readability; no external CSS dependencies */
      body { font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; margin: 24px; line-height: 1.4; }
      header { margin-bottom: 16px; }
      .muted { color: #555; }
      .error { color: #b00020; white-space: pre-wrap; }
      figure { margin: 0; }
      figcaption { margin-top: 8px; font-size: 0.875rem; }
      img { max-width: 100%; height: auto; border-radius: 8px; }
      iframe { width: 100%; aspect-ratio: 16 / 9; border: 0; border-radius: 8px; }
      .card { padding: 16px; border: 1px solid #ddd; border-radius: 10px; }
      dl { margin: 12px 0 0; }
      dt { font-weight: 600; margin-top: 10px; }
      dd { margin-left: 0; }
      code { background: #f6f6f6; padding: 2px 4px; border-radius: 4px; }
    </style>
  </head>

  <body>
    <header>
      <h1>NASA Astronomy Picture of the Day</h1>
      <p class="muted">
        This page fetches APOD directly from <code>https://api.nasa.gov/</code> using client-side JavaScript.
      </p>
    </header>

    <main class="card">
      <!-- Display status messages (loading / errors) -->
      <p id="status" class="muted">Loading APOD...</p>

      <!-- APOD content container -->
      <section id="apod" hidden>
        <h2 id="title"></h2>
        <p class="muted" id="date"></p>

        <!-- Display image or video -->
        <figure id="media"></figure>

        <!-- Description of the image or video -->
        <h3>Explanation</h3>
        <p id="explanation"></p>

        <h3>Details</h3>
        <dl>
          <dt>Media type</dt>
          <dd id="mediaType"></dd>

          <dt>HD URL</dt>
          <dd id="hdUrl"></dd>

          <dt>Copyright</dt>
          <dd id="copyright"></dd>

          <dt>Service version</dt>
          <dd id="serviceVersion"></dd>
        </dl>
      </section>

      <!-- Error container -->
      <p id="error" class="error" hidden></p>
    </main>
    
    <!-- Add APOD script code to this section -->
    <script>
    </script>

  </body>
</html>

API configuration

The API configuration in the APOD script defines the constants used to identify and authenticate NASA’s APOD API. This section of the script uses the configuration object pattern, which centralizes API settings so you can modify them easily in one place. The values are defined as constants because they shouldn’t change during script execution. For details about using constants, see MDN: const.

The configuration in the APOD script defines the following constants.

ConstantTypeDescription
NASA_API_KEYStringA string literal containing the key for authenticating the APOD API. DEMO_KEY is a special rate-limited key NASA provides for testing. For production, register at https://api.nasa.gov/ to get your own key.
APOD_ENDPOINTStringA string literal containing the base URL of the APOD API endpoint.

Together, these values form the complete API request URL. You can test the API by entering this URL directly in your browser:

https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY

Here’s the API configuration in the script:

const NASA_API_KEY = "DEMO_KEY";
const APOD_ENDPOINT = "https://api.nasa.gov/planetary/apod";

DOM references

Before retrieving content from the APOD API, the APOD script uses DOM caching to create and store DOM references to the HTML elements that display the content in the UI.

DOM (Document Object Model) references are variables that point to elements in a document. This allows the app to access and update them. DOM caching is a development pattern where apps store DOM references to avoid looking them up repeatedly during script execution. This improves performance by reducing document.getElementById() method calls.

The APOD script uses DOM caching by storing references to HTML elements as constants, which avoids repeated document.getElementById() method calls. The El suffix used by these constants indicates the constant is a DOM reference to an element in the UI.

The script caches the following DOM references. <dd> elements are description details elements used within a <dl> description list. See: MDN: dd element for details.

ConstantDescription
statusElA reference to the paragraph element that displays loading/status messages.
Used to show “Loading APOD…” and cleared after load completes.
errorElA reference to the paragraph element that displays error messages.
Hidden by default; shown only when an error occurs.
apodSectionA reference to the section element that contains all APOD content.
Uses the hidden attribute to show/hide the entire content block.
See: MDN: hidden attribute for details.
titleElA reference to the heading element for the APOD title.
dateElA reference to the paragraph element for the APOD date.
mediaElA reference to the <figure> element that contains the image or video.
See: MDN: figure element for details.
explanationElA reference to the paragraph element for the APOD explanation text.
mediaTypeElA reference to the <dd> element that displays the media type metadata.
hdUrlElA reference to the <dd> element that displays the HD image URL.
copyrightElA reference to the <dd> element that displays copyright information.
serviceVersionElA reference to the <dd> element that displays the API service version.

Here are the DOM references in the script:

const statusEl = document.getElementById("status");
const errorEl = document.getElementById("error");
const apodSection = document.getElementById("apod");
const titleEl = document.getElementById("title");
const dateEl = document.getElementById("date");
const mediaEl = document.getElementById("media");
const explanationEl = document.getElementById("explanation");
const mediaTypeEl = document.getElementById("mediaType");
const hdUrlEl = document.getElementById("hdUrl");
const copyrightEl = document.getElementById("copyright");
const serviceVersionEl = document.getElementById("serviceVersion");

Helper functions

The APOD script includes a set of helper functions, which are single-purpose utility functions called throughout the main application logic.

Here’s a description of each helper function.

FunctionDescription
setStatus(message)Updates the status element with a message, or hides it if empty.
• Parameter: message (string) - the text to display, or empty string to hide.
• Returns: undefined (no return value).
• Pattern: Encapsulates status display logic in one place.
showError(message)Displays an error message to the user.
• Parameter: message (string) - the error text to display.
• Returns: undefined.
clearError()Hides the error element and clears its content. This function is called before each API request to reset error state.
• Parameters: none.
• Returns: undefined.
createLink(url)A factory function that creates an <a> (anchor) element for external links.
• Parameter: url (string) - the URL for the href attribute.
• Returns: HTMLAnchorElement - the created <a> element.

Here are the helper functions in the script:

function setStatus(message) {
  statusEl.textContent = message;
  statusEl.hidden = !message;
}

function showError(message) {
  errorEl.textContent = message;
  errorEl.hidden = false;
}

function clearError() {
  errorEl.hidden = true;
  errorEl.textContent = "";
}

function createLink(url) {
  const a = document.createElement("a");
  a.href = url;
  a.textContent = url;
  a.target = "_blank";
  a.rel = "noopener noreferrer";
  return a;
}

Main application logic

The main application logic of the APOD script contains the core functions that fetch data from the APOD API (loadApod) and render the data in the UI (renderApod).

loadApod function

The loadApod() function uses the async/await pattern to fetch data from the APOD API and then passes the response to renderApod() for display. For details about the async/await pattern, see MDN: Promises.

loadApod() performs these tasks:

  • Builds the API request URL with the authentication key.
  • Sends the request to the APOD API.
  • Throws an error if the request fails.
  • Parses the JSON response into a data object.
  • Passes the data object to renderApod().
  • Displays an error message if an error occurs.
  • Shows the content section of the UI.

If the request to the APOD API succeeds, the API returns a JSON data object with the following info.

  • title — Image title
  • date — Date (YYYY-MM-DD)
  • explanation — Description text
  • media_type — Either “image” or “video”
  • url — URL of the image or video
  • hdurl — HD image URL (optional, images only)
  • copyright — Photographer name (optional)
  • service_version — API version

For details about the data object, see NASA’s APOD API on GitHub.

renderApod function

The renderApod() function updates the DOM to display the retrieved APOD data.

renderApod() performs these tasks:

  • Displays metadata (title, date, explanation, copyright).
  • Renders the image or video element.
  • Adds a caption to the media element.
  • Links to the HD image URL if available.

This function uses defensive programming by assigning fallback values when encountering missing data. The function also uses the textContent property instead of innerHTML to insert the APOD data into the UI. This prevents sites from using XSS (Cross-Site Scripting) on the code, which is a security vulnerability that allows an attacker to inject malicious code into a webpage.

Here’s the main application logic of the script:

async function loadApod() {
  const url = new URL(APOD_ENDPOINT);
  url.searchParams.set("api_key", NASA_API_KEY);

  setStatus("Loading APOD...");
  clearError();
  apodSection.hidden = true;

  try {
    const response = await fetch(url.toString(), {
      method: "GET",
      headers: {
        "Accept": "application/json"
      }
    });

    // fetch() does not reject on HTTP errors; check response.ok manually
    if (!response.ok) {
      const bodyText = await response.text();
      throw new Error(
        "APOD request failed.\n\n" +
        "HTTP " + response.status + " " + response.statusText + "\n\n" +
        "Response body:\n" + bodyText
      );
    }

    const data = await response.json();
    renderApod(data);
    setStatus("");
    apodSection.hidden = false;

  } catch (err) {
    setStatus("");
    showError(err.message || String(err));
  }
}

function renderApod(data) {
  // Use textContent (not innerHTML) for API data to prevent XSS
  titleEl.textContent = data.title || "NASA APOD";
  dateEl.textContent = data.date ? ("Date: " + data.date) : "";
  explanationEl.textContent = data.explanation || "";
  mediaTypeEl.textContent = data.media_type || "unknown";
  serviceVersionEl.textContent = data.service_version || "";
  copyrightEl.textContent = data.copyright || "Not provided";

  mediaEl.innerHTML = "";

  if (data.media_type === "image") {
    const img = document.createElement("img");
    img.src = data.url;
    img.alt = data.title || "NASA APOD image";
    img.loading = "lazy";
    mediaEl.appendChild(img);

  } else if (data.media_type === "video") {
    const iframe = document.createElement("iframe");
    iframe.src = data.url;
    iframe.title = data.title || "NASA APOD video";
    iframe.allow = "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share";
    iframe.allowFullscreen = true;
    mediaEl.appendChild(iframe);

  } else {
    // Fallback for unexpected media types
    const p = document.createElement("p");
    p.textContent = "Unsupported media type: " + String(data.media_type);
    mediaEl.appendChild(p);
  }

  const caption = document.createElement("figcaption");
  caption.className = "muted";
  caption.textContent = data.title || "";
  mediaEl.appendChild(caption);

  hdUrlEl.innerHTML = "";
  if (data.hdurl) {
    hdUrlEl.appendChild(createLink(data.hdurl));
  } else {
    hdUrlEl.textContent = "Not available";
  }
}

Entry point

The entry point of the script is the loadApod function call.

Here’s the code:

loadApod();

Complete code example

The following code is the complete example. You can save this code in an HTML file, and open it in your browser to view the Astronomy Picture of the Day from NASA’s APOD API.

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <meta name="description" content="Displays NASA's Astronomy Picture of the Day using the APOD API." />
    <title>NASA APOD (Client-side Example)</title>
    <style>
      /* Minimal styling for readability; no external CSS dependencies */
      body { font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; margin: 24px; line-height: 1.4; }
      header { margin-bottom: 16px; }
      .muted { color: #555; }
      .error { color: #b00020; white-space: pre-wrap; }
      figure { margin: 0; }
      figcaption { margin-top: 8px; font-size: 0.875rem; }
      img { max-width: 100%; height: auto; border-radius: 8px; }
      iframe { width: 100%; aspect-ratio: 16 / 9; border: 0; border-radius: 8px; }
      .card { padding: 16px; border: 1px solid #ddd; border-radius: 10px; }
      dl { margin: 12px 0 0; }
      dt { font-weight: 600; margin-top: 10px; }
      dd { margin-left: 0; }
      code { background: #f6f6f6; padding: 2px 4px; border-radius: 4px; }
    </style>
  </head>

  <body>
    <header>
      <h1>NASA Astronomy Picture of the Day</h1>
      <p class="muted">
        This page fetches APOD directly from <code>https://api.nasa.gov/</code> using client-side JavaScript.
      </p>
    </header>

    <main class="card">
      <!-- Display status messages (loading / errors) -->
      <p id="status" class="muted">Loading APOD...</p>

      <!-- APOD content container -->
      <section id="apod" hidden>
        <h2 id="title"></h2>
        <p class="muted" id="date"></p>

        <!-- Display image or video -->
        <figure id="media"></figure>

        <!-- Description of the image or video -->
        <h3>Explanation</h3>
        <p id="explanation"></p>

        <h3>Details</h3>
        <dl>
          <dt>Media type</dt>
          <dd id="mediaType"></dd>

          <dt>HD URL</dt>
          <dd id="hdUrl"></dd>

          <dt>Copyright</dt>
          <dd id="copyright"></dd>

          <dt>Service version</dt>
          <dd id="serviceVersion"></dd>
        </dl>
      </section>

      <!-- Error container -->
      <p id="error" class="error" hidden></p>
    </main>
    
    <!-- APOD script code -->
    <script>
      const NASA_API_KEY = "DEMO_KEY";
      const APOD_ENDPOINT = "https://api.nasa.gov/planetary/apod";

      const statusEl = document.getElementById("status");
      const errorEl = document.getElementById("error");
      const apodSection = document.getElementById("apod");
      const titleEl = document.getElementById("title");
      const dateEl = document.getElementById("date");
      const mediaEl = document.getElementById("media");
      const explanationEl = document.getElementById("explanation");
      const mediaTypeEl = document.getElementById("mediaType");
      const hdUrlEl = document.getElementById("hdUrl");
      const copyrightEl = document.getElementById("copyright");
      const serviceVersionEl = document.getElementById("serviceVersion");

      function setStatus(message) {
        statusEl.textContent = message;
        statusEl.hidden = !message;
      }

      function showError(message) {
        errorEl.textContent = message;
        errorEl.hidden = false;
      }

      function clearError() {
        errorEl.hidden = true;
        errorEl.textContent = "";
      }

      function createLink(url) {
        const a = document.createElement("a");
        a.href = url;
        a.textContent = url;
        a.target = "_blank";
        a.rel = "noopener noreferrer";
        return a;
      }

      async function loadApod() {
        const url = new URL(APOD_ENDPOINT);
        url.searchParams.set("api_key", NASA_API_KEY);

        setStatus("Loading APOD...");
        clearError();
        apodSection.hidden = true;

        try {
          const response = await fetch(url.toString(), {
            method: "GET",
            headers: {
              "Accept": "application/json"
            }
          });

          // fetch() does not reject on HTTP errors; check response.ok manually
          if (!response.ok) {
            const bodyText = await response.text();
            throw new Error(
              "APOD request failed.\n\n" +
              "HTTP " + response.status + " " + response.statusText + "\n\n" +
              "Response body:\n" + bodyText
            );
          }

          const data = await response.json();
          renderApod(data);
          setStatus("");
          apodSection.hidden = false;

        } catch (err) {
          setStatus("");
          showError(err.message || String(err));
        }
      }

      function renderApod(data) {
        // Use textContent (not innerHTML) for API data to prevent XSS
        titleEl.textContent = data.title || "NASA APOD";
        dateEl.textContent = data.date ? ("Date: " + data.date) : "";
        explanationEl.textContent = data.explanation || "";
        mediaTypeEl.textContent = data.media_type || "unknown";
        serviceVersionEl.textContent = data.service_version || "";
        copyrightEl.textContent = data.copyright || "Not provided";

        mediaEl.innerHTML = "";

        if (data.media_type === "image") {
          const img = document.createElement("img");
          img.src = data.url;
          img.alt = data.title || "NASA APOD image";
          img.loading = "lazy";
          mediaEl.appendChild(img);

        } else if (data.media_type === "video") {
          const iframe = document.createElement("iframe");
          iframe.src = data.url;
          iframe.title = data.title || "NASA APOD video";
          iframe.allow = "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share";
          iframe.allowFullscreen = true;
          mediaEl.appendChild(iframe);

        } else {
          // Fallback for unexpected media types
          const p = document.createElement("p");
          p.textContent = "Unsupported media type: " + String(data.media_type);
          mediaEl.appendChild(p);
        }

        const caption = document.createElement("figcaption");
        caption.className = "muted";
        caption.textContent = data.title || "";
        mediaEl.appendChild(caption);

        hdUrlEl.innerHTML = "";
        if (data.hdurl) {
          hdUrlEl.appendChild(createLink(data.hdurl));
        } else {
          hdUrlEl.textContent = "Not available";
        }
      }

      loadApod();
    </script>

  </body>
</html>