Project: Movie Search App

In the previous lesson, we built a notes app that saved data in the browser. Now let’s build our final project, a movie search app that talks to a real API on the internet and shows the results on screen.

🎯 What We’ll Build

We’ll build a movie search app. The user types a movie title, presses search, and the app fetches matching movies from a public movie API. Each match shows up as a card with the title, the year, and the poster image.

This project pulls together everything from the course at once: the DOM to read the input and build cards, events to react to the search, fetch and async/await to talk to the API, JSON to read the response, map and createElement to render the results, and try/catch to handle errors.

We’ll use the OMDb API, a free movie database. It returns movie data as JSON, which is exactly the shape our app needs.

🧱 The HTML

The page needs three things: a text input for the title, a button to start the search, and a grid where the result cards go.

index.html
<div class="search-app">
<div class="search-bar">
<input id="search-input" type="text" placeholder="Search for a movie..." />
<button id="search-button">Search</button>
</div>
<div id="results" class="results-grid"></div>
</div>
<script src="movie-search-app.js"></script>

Let’s walk through what each part of the markup does:

  • <input id="search-input"> is the text box where the user types a movie title. The id lets us grab it from JavaScript.
  • <button id="search-button"> starts the search when clicked. It also has an id so we can listen for clicks on it.
  • <div id="results"> is the empty grid where cards go. We start it empty and fill it after each search.
  • <script src="movie-search-app.js"> loads our JavaScript file. We place it at the bottom so the elements above already exist when the script runs.

⚙️ The JavaScript

Here is the full script. Read it once, then we’ll walk through it line by line.

movie-search-app.js
const API_KEY = "your_api_key_here";
const API_URL = "https://www.omdbapi.com/";
const input = document.querySelector("#search-input");
const button = document.querySelector("#search-button");
const results = document.querySelector("#results");
async function searchMovies(query) {
results.innerHTML = "<p>Loading...</p>";
try {
const url = `${API_URL}?apikey=${API_KEY}&s=${encodeURIComponent(query)}`;
const response = await fetch(url);
const data = await response.json();
if (data.Response === "False") {
results.innerHTML = `<p>No movies found for "${query}".</p>`;
return;
}
renderMovies(data.Search);
} catch (error) {
results.innerHTML = "<p>Something went wrong. Please try again.</p>";
console.error(error);
}
}
function renderMovies(movies) {
results.innerHTML = "";
const cards = movies.map((movie) => {
const card = document.createElement("div");
card.className = "movie-card";
const poster =
movie.Poster !== "N/A"
? movie.Poster
: "https://via.placeholder.com/300x445?text=No+Poster";
card.innerHTML = `
<img src="${poster}" alt="${movie.Title} poster" />
<h3>${movie.Title}</h3>
<p>${movie.Year}</p>
`;
return card;
});
results.append(...cards);
}
button.addEventListener("click", () => {
const query = input.value.trim();
if (query) {
searchMovies(query);
}
});
input.addEventListener("keydown", (event) => {
if (event.key === "Enter") {
button.click();
}
});

Let’s walk through what each part does, from the top down:

  • The setup lines. We store the API key and base URL as constants, then grab the input, button, and results grid from the page with querySelector.
  • async function searchMovies(query). This function does the real work. We mark it async so we can use await inside it. It first shows a “Loading…” message so the user knows something is happening.
  • Building the request URL. The line const url = ... adds the API key and the search term to the base URL. We wrap the search term in encodeURIComponent so spaces and special characters become safe to put in a URL.
  • await fetch(url). This sends the request and waits for the server to answer. await response.json() then reads the body and turns the JSON text into a JavaScript object.
  • The no-results check. The OMDb API tells us whether the search worked through a Response field. When it is the string "False", there were no matches, so we show a no-results message and stop with return.
  • renderMovies(data.Search). When the search succeeds, we pass the array of movies to renderMovies to draw the cards.
  • The renderMovies function. It clears the grid, then uses map to turn each movie object into a card element built with createElement. We check the poster: when the API has no image it sends the string "N/A", so we swap in a placeholder image instead. Finally results.append(...cards) drops all the cards into the grid at once.
  • The try/catch block. The whole fetch lives inside it. If the network fails or the server is unreachable, the catch runs, shows a friendly error message, and logs the real error to the console for us to debug.
  • Wiring up the events. At the bottom, clicking the button reads the input, trims off blank spaces, and searches only when there is something to search for. Pressing Enter in the input triggers the same button click, so the keyboard works too.

Get a free API key

The OMDb API needs a key, and it is free. Visit omdbapi.com/apikey.aspx, enter your email, and click the link they send you to activate the key. Then paste it into the API_KEY constant at the top of the script. Never commit a real key to a public repository.

🚀 Try It Yourself!

The app works, but there is plenty of room to grow it. Try these enhancements.

  1. Add pagination. The OMDb API returns ten results per page and includes a totalResults count, plus a &page= parameter. Add “Previous” and “Next” buttons that change the page number and search again.
  2. Add a details view. When a card is clicked, fetch that single movie by its imdbID (use &i= instead of &s=) and show its full plot, rating, and cast in a panel.
  3. Add debounced search. Instead of a button, search as the user types, but wait until they stop typing using the technique from the debouncing lesson so you do not fire a request on every keystroke.

🧩 What You’ve Learned

  • ✅ How to call a real API with fetch and async/await
  • ✅ How to turn a JSON response into JavaScript with response.json()
  • ✅ How to render a list of results as cards using map and createElement
  • ✅ How to handle the no-results case and network errors with try/catch
  • ✅ How to build a safe request URL with encodeURIComponent

🎉 Congratulations!

You finished the JavaScript course. That is a real milestone, so take a moment to look back at how far you have come.

You started with the basics, variables, types, and operators. You learned to write functions, then to work with arrays and objects to organize data. You used the DOM to read and change the page, handled events, and learned async JavaScript with promises and fetch. You explored objects and classes, and you tied it all together by building real projects like this movie search app.

You now have the skills to read, write, and ship working JavaScript. The natural next step is to learn a framework that builds on these foundations and makes large apps far easier to manage.

Continue your journey with the React course, where you will use everything you learned here to build modern, component-based interfaces. Great work, and see you there.

Share & Connect