Project: Movie Search App
Table of Contents + −
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.
<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. Theidlets us grab it from JavaScript.<button id="search-button">starts the search when clicked. It also has anidso 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.
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 itasyncso we can useawaitinside 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 inencodeURIComponentso 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
Responsefield. When it is the string"False", there were no matches, so we show a no-results message and stop withreturn. renderMovies(data.Search). When the search succeeds, we pass the array of movies torenderMoviesto draw the cards.- The
renderMoviesfunction. It clears the grid, then usesmapto turn each movie object into a card element built withcreateElement. We check the poster: when the API has no image it sends the string"N/A", so we swap in a placeholder image instead. Finallyresults.append(...cards)drops all the cards into the grid at once. - The
try/catchblock. The whole fetch lives inside it. If the network fails or the server is unreachable, thecatchruns, 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.
- Add pagination. The OMDb API returns ten results per page and includes a
totalResultscount, plus a&page=parameter. Add “Previous” and “Next” buttons that change the page number and search again. - 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. - 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
fetchandasync/await - ✅ How to turn a JSON response into JavaScript with
response.json() - ✅ How to render a list of results as cards using
mapandcreateElement - ✅ 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.