Skip to main content
JavaScript Basics
CHAPTER 30 Beginner

Final JavaScript Project

Updated: May 12, 2026
45 min read

# Final JavaScript Project: Movie Search App

Congratulations on making it to the final chapter of the JavaScript Basics course! You have learned everything from variables and loops to DOM manipulation, APIs, and modern ES6 syntax.

It is time to put all of these concepts together to build a complete, professional web application from scratch. We will build a Movie Search Application.

1. Project Overview

We will build an application that allows users to type in the name of a movie, hits a public API to find matching movies, and dynamically renders the results onto the screen as beautiful cards.

Concepts Used:

  • DOM Selection & Manipulation
  • Event Listeners (submit)
  • Form Validation
  • Fetch API (async / await)
  • Template Literals
  • Array Iteration (forEach / map)
  • Error Handling (try...catch)

2. Setting Up the HTML

We need a clean, structured HTML file. We will have a header, a search form, a loading spinner, an error message container, and a grid container for our movie cards.

index.html

html
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Movie Finder Pro</title>
    <style>
        /* CSS resets and base styles */
        * { box-sizing: border-box; margin: 0; padding: 0; font-family: &#039;Segoe UI', Tahoma, Geneva, Verdana, sans-serif; }
        body { background-color: #121212; color: #ffffff; padding: 20px; }
        
        /* Header & Form */
        header { text-align: center; margin-bottom: 30px; }
        header h1 { color: #f5c518; margin-bottom: 15px; font-size: 2.5rem; }
        #searchForm { display: flex; justify-content: center; max-width: 600px; margin: 0 auto; }
        #searchInput { width: 70%; padding: 15px; border: none; border-radius: 4px 0 0 4px; font-size: 16px; outline: none; }
        #searchBtn { width: 30%; padding: 15px; background-color: #f5c518; color: #000; border: none; border-radius: 0 4px 4px 0; font-size: 16px; font-weight: bold; cursor: pointer; transition: 0.2s;}
        #searchBtn:hover { background-color: #e0b416; }

        /* Status Messages */
        .status-msg { text-align: center; margin: 20px 0; font-size: 1.2rem; }
        .error { color: #ff4d4d; }
        .hidden { display: none; }

        /* Movie Grid */
        #movieGrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 25px; max-width: 1000px; margin: 0 auto; padding-top: 20px; }
        
        /* Movie Card */
        .movie-card { background: #1f1f1f; border-radius: 8px; overflow: hidden; box-shadow: 0 4px 15px rgba(0,0,0,0.5); transition: transform 0.3s; }
        .movie-card:hover { transform: translateY(-5px); }
        .movie-card img { width: 100%; height: 300px; object-fit: cover; background: #333;}
        .movie-info { padding: 15px; }
        .movie-title { font-size: 1.1rem; font-weight: bold; margin-bottom: 5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;}
        .movie-year { color: #aaa; font-size: 0.9rem; }
    </style>
</head>
<body>

    <header>
        <h1>Movie Finder Pro</h1>
        <form id="searchForm">
            <input type="text" id="searchInput" placeholder="Search for a movie (e.g., Batman)..." autocomplete="off">
            <button type="submit" id="searchBtn">Search</button>
        </form>
    </header>

    <main>
        <div id="loader" class="status-msg hidden">Searching database...</div>
        <div id="errorMsg" class="status-msg error hidden"></div>
        <div id="movieGrid"></div>
    </main>

    <script src="app.js"></script>
</body>
</html>

---

3. Writing the JavaScript Logic

Now we will write the logic. We will use the free OMDb API (Open Movie Database). *(Note: For this example, we use a public demo key. In a real app, you would sign up for your own API key).*

app.js

javascript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
"use strict"; // Enforce best practices!

// 1. DOM Elements
const searchForm = document.getElementById("searchForm");
const searchInput = document.getElementById("searchInput");
const movieGrid = document.getElementById("movieGrid");
const loader = document.getElementById("loader");
const errorMsg = document.getElementById("errorMsg");

// OMDb API URL (Using public demo key for educational purposes)
const API_URL = "https://www.omdbapi.com/?apikey=4a3b711b&s=";

// 2. Event Listeners
searchForm.addEventListener("submit", function(event) {
    event.preventDefault(); // Stop page refresh
    const searchTerm = searchInput.value.trim();

    // Form Validation
    if (searchTerm === "") {
        showError("Please enter a movie name.");
        return;
    }

    // Call our async fetch function
    fetchMovies(searchTerm);
});

// 3. The Async Fetch Function
async function fetchMovies(query) {
    // Reset UI before new search
    movieGrid.innerHTML = ""; 
    hideError();
    showLoader();

    try {
        // AWAIT the network request
        const response = await fetch(API_URL + query);
        
        // Manual Error check for bad network
        if (!response.ok) {
            throw new Error("Network response was not ok");
        }

        // AWAIT JSON parsing
        const data = await response.json();

        // OMDb API returns "Response: 'False'" if movie not found
        if (data.Response === "False") {
            throw new Error(data.Error); // "Movie not found!"
        }

        // Success! Pass the array of movies to our render function
        renderMovies(data.Search);

    } catch (error) {
        // Catch all errors (network failures or 'movie not found')
        showError(error.message);
    } finally {
        // Hide loader whether success or failure
        hideLoader();
    }
}

// 4. Render the Data to the DOM
function renderMovies(moviesArray) {
    let finalHTML = "";

    // Iterate through the array using modern for...of loop
    for (const movie of moviesArray) {
        
        // Handle missing posters gracefully
        const posterImage = movie.Poster !== "N/A" ? movie.Poster : "https://via.placeholder.com/300x450?text=No+Poster";

        // Build HTML string using Template Literals
        finalHTML += `
            <div class="movie-card">
                <img src="${posterImage}" alt="${movie.Title}">
                <div class="movie-info">
                    <div class="movie-title">${movie.Title}</div>
                    <div class="movie-year">${movie.Year}</div>
                </div>
            </div>
        `;
    }

    // Inject into the DOM exactly ONCE
    movieGrid.innerHTML = finalHTML;
}

// 5. UI Helper Functions
function showLoader() {
    loader.classList.remove("hidden");
}

function hideLoader() {
    loader.classList.add("hidden");
}

function showError(message) {
    errorMsg.textContent = message;
    errorMsg.classList.remove("hidden");
}

function hideError() {
    errorMsg.classList.add("hidden");
}

4. Code Breakdown & Review

Let's review why this code is written the way it is:

  1. 1. Separation of Concerns: The code is cleanly divided into DOM Selection, Event Listening, Data Fetching, Rendering, and UI manipulation.
  1. 2. event.preventDefault(): We prevent the <form> from reloading the page when the user presses Enter.
  1. 3. async / await & try...catch: We safely handle the network request. If the internet drops or the movie doesn't exist, the catch block catches the error and our showError helper displays it beautifully to the user.
  1. 4. DOM Optimization: In renderMovies, we do NOT use movieGrid.innerHTML += ... inside the loop. That would force the browser to repaint the screen 10 times. Instead, we build a giant finalHTML string in memory, and update the DOM exactly once at the very end.
  1. 5. Fallback Images: We check if movie.Poster === "N/A". If an API misses an image, displaying a broken image icon looks unprofessional. We supply a fallback URL.

5. Course Conclusion

You have successfully completed the JavaScript Basics course!

You started by learning what a variable is, and you have finished by building a modern, asynchronous web application that pulls live data from the internet.

Where to go from here?

  • Practice: The only way to truly master JavaScript is to build things. Build a To-Do list, a Weather app, or a simple browser game.
  • Advanced Frameworks: Now that you know native Vanilla JavaScript, you are ready to learn modern frameworks like React, Vue.js, or Node.js for backend development.

Keep coding, keep breaking things, and keep learning!

Finish this Chapter

Save your progress on your learning path and prepare for coding interview challenges.

Discussion

Join the discussion

Log in or create a free account to participate.

Sort: ·