DEV Community

Cover image for Mastering API Fetch: How to Build a Movie Website with Real-Time Data Updates
Tracy | Software Engineer
Tracy | Software Engineer

Posted on

Mastering API Fetch: How to Build a Movie Website with Real-Time Data Updates

Learn how to use the Fetch API to asynchronously request data from an external API and dynamically build a movie website. This beginner-friendly tutorial is great for learning about APIs, async JS, and building dynamic front-end applications.

This article will explore how API fetch works while building a movie website.

What Is Fetch?

Fetch is a web API provided by modern browsers that allow developers to make HTTP requests to servers and retrieve data or resources from them.

If you have worked with XMLHttpRequest (XHR) object, the Fetch API can perform all the tasks as the XHR object does.

Using the Fetch API

To use Fetch, developers can call the global fetch() function and pass in a URL to the resource they want to fetch.

fetch('https://apiexample.com/data')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error(error));
Enter fullscreen mode Exit fullscreen mode
  • The above code retrieves JSON data from a URL called https://apiexample.com/data and parses it as JSON using the .json() method, If an error occurs during the fetch, the catch() method logs an error message to the console.

Features of the movie website

Using TMDB API to display movie posters and information on the movie card.

Building a functional search bar to search for any movie and display results.

A modal box that displays details of the movie when you click on the movie card.

The layout of the website

  • The website would have a header, main, and footer.
  • The Header consists of the movie title and a search bar to search for movies.
  • The Search Bar
  • Main - This is where the movie card will be displayed.
  • Footer - The footer will carry copyright text along with the name of the developer.

The Header

Copy and paste this code into your code editor.

<header class="header">
        <div class="movieheader">
            <p style="display: flex; align-items: center;">🎥any<span style="color: hsla(349, 69%, 51%, 1);">movie</span></p>
        </div>

        <form action="/" class="search-btn">
            <input type="text" placeholder="search for a movie...">
        </form>
    </header>
Enter fullscreen mode Exit fullscreen mode

The HTML tag consists of the movie title with the class name "movie header" and a search bar "search-btn".

The input field for the search bar is wrapped up in a form container because it provides a way to submit data to a server for processing. When a user enters a movie query into the search bar and hits "Enter", the data entered into the input field is packaged into an HTTP request and sent to a server for processing.

*,
*::before,
*::after{
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}
body{
    font-family: 'Ubuntu', sans-serif;
    background-color: rgb(11, 12, 13);
}

.movieheader{
    font-size: 1.2rem;
    color: hsla(200, 100%, 95%, 1);
}
.movieheader p{
    font-size: 1.2rem;
    font-weight: 600;
}

.header{
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 95%;
    margin: 1.4rem auto;
}
.search-btn{
    align-items: center;
    border-radius: 5px;
}
.search-btn input{
    outline: none;
    border: none;
    padding: .5rem .3rem;
    font-size: .9rem;
    background-color: hsla(250, 6%, 20%, 1);
    color: hsla(200, 100%, 95%, 1);
    border-radius: 5px;
}


@media screen and (min-width: 750px) {
    .header{
        width: 90%;
        padding: 1rem 0;
    }
    .movieheader p{
        font-size: 1.7rem;
    }
    .search-btn input{
        width: 50vh;
    }
    .sm-search{
        display: none;
    }
    .search-btn input{
        padding: .8rem .5rem;
        font-size: 1rem;
    }
}



@media screen and (min-width: 1024px) {
    .header{
        width: 90%;
        padding: 1rem 0;
    }
    .movieheader p{
        font-size: 1.7rem;
    }
    .search-btn input{
        width: 50vh;
    }
    .sm-search{
        display: none;
    }
    .search-btn input{
        padding: .8rem .5rem;
        font-size: 1rem;
    }
}
Enter fullscreen mode Exit fullscreen mode
  • It has a dark background color of rgb(11, 12, 13). On the Desktop screen, the font size of the movie title is font-size: 1.7rem; and on a mobile screen, the font size is 1.2rem.
  • I added a media query for responsiveness.

The Search Bar Functionality

<form action="/" class="search-btn">
   <input type="text" placeholder="search for a movie...">
</form>
Enter fullscreen mode Exit fullscreen mode
  • I used the <form> tag as a container for the search bar input field because it provides a way to submit the search query to a server for processing.
const form = document.querySelector('.search-btn');
const movieCon = document.querySelector('.movieContainer');
const input = form.querySelector('input');
const searchResults = document.querySelector('.search-result');
const searchResultContainer = document.querySelector('.searchResultContainer');


form.addEventListener('submit', async (event) => {
  event.preventDefault();
  const query = input.value;
  const apiKey = 'YOUR_API_KEY'; // replace with your actual API key
  const apiUrl = `https://api.themoviedb.org/3/search/movie?api_key=${apiKey}&query=${query}`;

  try {
    const response = await fetch(apiUrl);
    const data = await response.json();
    const movies = data.results;

    searchResults.innerHTML = '';
    movieCon.innerHTML = '';


    movies.forEach((movie) => {
      const { poster_path, title, release_date, vote_average } = movie;

      const movieContainer = document.createElement('div');
      movieContainer.classList.add('movieCard');

      const posterUrl = poster_path
        ? `https://image.tmdb.org/t/p/w500/${poster_path}`
        : 'https://via.placeholder.com/500x750?text=No+poster';

      movieContainer.innerHTML = `
        <img src="${posterUrl}" alt="${title}" class="moviePoster">
        <div class="movieTitle">${title}</div>
        <div class="rating">
          <span class="rate">${vote_average}</span>
          <span class="year">${release_date ? release_date.slice(0, 4) : ''}</span>
        </div>
      `;

      searchResults.appendChild(movieContainer);
    });

    const header = searchResultContainer.querySelector('header');
    if (!header) {
      const header = document.createElement('header');
      header.innerHTML = `<h1>Search Results: ${query}</h1>`;
      searchResultContainer.insertBefore(header, searchResults);
    } else {
      header.innerHTML = `<h1>Search Results: ${query}</h1>`;
    }
  } catch (error) {
    console.error(error);
  }

});
Enter fullscreen mode Exit fullscreen mode
  • This above code sets up a search feature for movies using The Movie Database TMDB API. It starts by selecting relevant elements from the HTML document using their class names (form, movieCon, input, searchResults, and searchResultContainer).
  • The code then adds an event listener to the form element for the submit event. When the user submits the form, the event listener function is called. The function first prevents the default form submission behavior with event.preventDefault().
  • It then retrieves the value of the user's search query from the input element and constructs the API endpoint URL with the query and the TMDB API key.
  • The code then sends a request to the TMDB API using fetch(). When the response is received, it is converted from JSON format to a JavaScript object using response.json(). The object is then processed to extract an array of movies matching the search query.
  • The movies array is then looped over using forEach(), and for each movie, a div element with the movie details is created using document.createElement(). The div element is then populated with movie data and appended to the searchResults element.
  • The code then checks if there is already a header element in the searchResultContainer. If there isn't, a new header element is created and inserted before the searchResults element. If there is, the h1 element of the header is updated with the new search query.
  • If any errors occur during the API request and response process, they are logged to the console using console.error().

Displaying the Movies - The Main

I will be using TMDB API to display movies on the website. Before that, create a movie container in the HTML body for the movie card and details.

<div class="movieContainer">
        <div class="movieCard">
            <img src="" alt="" class="moviePoster">
            <div class="movieTitle">John Wick</div>

            <div class="rating">
                <span class="rate"></span>
                <span class="year"></span>
            </div>
        </div>
    </div>
Enter fullscreen mode Exit fullscreen mode

NOTE: If you don't have an API Key, you would be required to sign up on the TMDB website.

Styling The Movie Container

/*Movie container for mobile screen*/
.movieContainer{
    display: flex;
    justify-content: center;
    flex-direction: column;
    width: 90%;
    margin: auto;
    margin-bottom: 5rem;
    padding-bottom: 2rem;
}
.movieCard{
    margin: .5rem 0;
}

.moviePoster{
    width: 100%;
    height: 40vh;
    object-fit: cover;
}

.movieTitle{
    font-size: 1rem;
    font-weight: 600;
    margin: .6rem 0;
}

.rating{
    display: flex;
    justify-content: space-between;
    color: gray;
    font-weight: 600;
}

@media screen and (min-width: 1024px) {
.movieContainer{
    flex-direction: row;
    flex-wrap: wrap;
}

.movieCard{
    margin: .6rem;
    width: 200px;
}
Enter fullscreen mode Exit fullscreen mode
  • On mobile screen. the flex-direction of the movie container is set to column. So it displays one movie per column. While on the Desktop, it displays 6 movie cards per row.

Javascript Functionality For API Call To Display Movies

const apiKey = 'YOUR_API_KEY';

fetch(`https://api.themoviedb.org/3/movie/popular?api_key=${apiKey}`)
  .then(response => response.json())
  .then(data => {
    console.log(data)
    const movies = data.results;
    const movieContainer = document.querySelector('.movieContainer');

    movies.forEach(movie => {
      const movieCard = document.createElement('div');
      movieCard.classList.add('movieCard');

      const moviePoster = document.createElement('img');
      moviePoster.classList.add('moviePoster');
      moviePoster.src = `https://image.tmdb.org/t/p/w500${movie.poster_path}`;
      moviePoster.alt = movie.title;

      const movieTitle = document.createElement('div');
      movieTitle.classList.add('movieTitle');
      movieTitle.innerText = movie.title;

      const rating = document.createElement('div');
      rating.classList.add('rating');

      const rate = document.createElement('span');
      rate.classList.add('rate');
      rate.innerText = `${movie.vote_average}/10`;

      const year = document.createElement('span');
      year.classList.add('year');
      year.innerText = movie.release_date.split('-')[0];

      rating.appendChild(rate);
      rating.appendChild(year);

      movieCard.appendChild(moviePoster);
      movieCard.appendChild(movieTitle);
      movieCard.appendChild(rating);

      movieContainer.appendChild(movieCard);
    });
  })
  .catch(error => {
    console.error('Error fetching movies:', error);
  });
Enter fullscreen mode Exit fullscreen mode

The above code will display popular movies including movie images, title, rating, and year of release. Here is how I went about it:

  • The line defines a variable to store your API Key.
const apiKey = 'YOUR_API_KEY';
Enter fullscreen mode Exit fullscreen mode
  • The fetch function calls the URL of the API endpoint as a parameter, with the apiKey variable inserted into the URL string using template literals - {apiKey}.
  • The fetch function returns a Promise, which is resolved with the response data obtained from the API. The response is then parsed as JSON using the json() method, which also returns a Promise.
fetch(`https://api.themoviedb.org/3/movie/popular?api_key=${apiKey}`)
    .then(response => response.json())
    .then(data =>
Enter fullscreen mode Exit fullscreen mode
  • The container element for the movies is obtained using document.querySelector(), and then a loop is used to iterate over each movie in the list.
const movieContainer = document.querySelector('.movieContainer');
Enter fullscreen mode Exit fullscreen mode
  • For each movie, a new div element is created using document.createElement(), with a class of movieCard. An img element is also created for the movie poster, with a class of moviePoster and an alt attribute set to the movie title.
movies.forEach(movie => {
      const movieCard = document.createElement('div');
      movieCard.classList.add('movieCard');

      const moviePoster = document.createElement('img');
      moviePoster.classList.add('moviePoster');
      moviePoster.src = `https://image.tmdb.org/t/p/w500${movie.poster_path}`;
      moviePoster.alt = movie.title;
}
Enter fullscreen mode Exit fullscreen mode

Two additional elements are created for the movie title and its rating, with classes of movieTitle and rating, respectively. The rating element contains two child span elements for the rating and year of release, with classes of rate and year, respectively. The movie poster, title, and rating elements are then added as child elements of the movieCard div. Finally, the movieCard div is added as a child element of the movie container element obtained earlier.

const movieTitle = document.createElement('div');
      movieTitle.classList.add('movieTitle');
      movieTitle.innerText = movie.title;

      const rating = document.createElement('div');
      rating.classList.add('rating');

      const rate = document.createElement('span');
      rate.classList.add('rate');
      rate.innerText = `${movie.vote_average}/10`;

      const year = document.createElement('span');
      year.classList.add('year');
      year.innerText = movie.release_date.split('-')[0];

      rating.appendChild(rate);
      rating.appendChild(year);

      movieCard.appendChild(moviePoster);
      movieCard.appendChild(movieTitle);
      movieCard.appendChild(rating);

      movieContainer.appendChild(movieCard);
    });
  })
Enter fullscreen mode Exit fullscreen mode

Building A Box Modal To display Movie details

*I thought about creating a modal box to display the movie details, what is your thought about doing in another article? Let me know what you think about that.
*

If you have a question, do let me know in the comment section. Also like and follow me if you found this article helpful.

The Footer

<footer>
   <div class="footerContainer">
    <p>Copyright 2023</p>
    <p>Created By Tracy</p>            
    </div>     
</footer>
Enter fullscreen mode Exit fullscreen mode
  • The footer tag contains two paragraph tags stating the name of the developer and a copyright text.
footer{
    background-color: #A01D1E;
    font-weight: 600;
    margin-top: 1rem;
    padding: 1rem 1.1rem;
}
.footerContainer{
    display: flex;
    justify-content: space-between;
}
Enter fullscreen mode Exit fullscreen mode

@media screen and (min-width: 1024px){
.footerContainer{
justify-content: space-around;
}
}

  • On a mobile screen, the spacing of the paragraph tag is set to space-between, while on a desktop, it is set to space-around.

The Final Result

The final result on Codepen

Top comments (10)

Collapse
 
sebastian_wessel profile image
Sebastian Wessel • Edited

As far as I know - maybe I'm wrong - fetch does not throw like axios or got lib on http errors like 404.

There is a result.ok which can/should be used to evaluate that the response is some 200 and not some 404 or 500. And in case of an error, you can simply use response.json() to get the error response sent from server - much easier than with axios

So the try-catch-block might not work as you expected here.

Collapse
 
tracy4code profile image
Tracy | Software Engineer

Thanks for the heads up. I will keep that in mind.

Collapse
 
sebastian_wessel profile image
Sebastian Wessel

Yeah, it’s a bit unexpected here, because it follows more the Rust language response handling and not the typical JavaScript-simply-throw pattern.

I only mentioned it, because I was doing it wrong, lost quite some time on it - one or two weeks ago 🤫😂

Thread Thread
 
tracy4code profile image
Tracy | Software Engineer

Well, it is not something I am familiar with so I had to check it out. It actually works so thanks.

Collapse
 
emma32 profile image
emma devid

It's a bit surprising here, as it aligns more with Rust's response handling rather than the typical JavaScript 'simply throw' pattern. I bring it up because I recently encountered this myself, spending quite some time on it about a week or two ago.
Regards: sonic happy hour

Collapse
 
emma32 profile image
emma devid

Discover the ultimate Goku experience with our Goku to Movies app! Immerse yourself in the world of epic battles and heroic deeds as you stream your favorite Goku movies and episodes. Download now to join Goku on his legendary quest and unleash the power of the Saiyan warrior within!

Collapse
 
clark profile image
Ronda

Great tutorial for beginners! Explains Fetch API and dynamic website creation well. To add, tamildhool app apk download free offers a similar concept for Indian movie lovers, providing real-time updates and a user-friendly interface for watching Tamil movies on the go.

Collapse
 
josephfisher profile image
Joseph Fisher

Thanks for sharing, I will definitely try this on Goku.to Movies.

Collapse
 
nabiullah748957 profile image
Nabi Ullah

Stream your favorite movies anytime, anywhere with Goku.tu Movies App your ultimate destination for cinematic delights.

Collapse
 
nabiullah748957 profile image
Nabi Ullah • Edited

Unlock a world of cinematic wonders with Goku.tu App, boasting an extensive library of films across genres.