Open In App

Build a Movie App with VueJS

Last Updated : 13 May, 2024
Comments
Improve
Suggest changes
Like Article
Like
Report

We will build a movie application using Vue.js. By the end, you'll have a fully functional movie app that will allow users to search for movies, view their details, and get information such as title, year, rating, and plot.

Prerequisites:

Approach

  • Vue Components: We will create two Vue components, MovieSearch and MovieDetail.
  • HTTP Requests: Axios is used to make API calls to OMDB.
  • Conditional Rendering: We will implement conditional rendering to display search results or movie details based on user interactions.

Steps to Create Application

Step 1: Setup Vue Project

Create a new Vue project using Vue CLI.

vue create movie-mania

Step 2: Install Axios

Axios is used for making HTTP requests. Install it in your project.

npm install axios

Updated dependencies will look like

"dependencies": {
"axios": "^1.6.8",
"core-js": "^3.8.3",
"vue": "^3.2.13"
},

Step 3: Creating and managing files and folders

  • Create MovieDetail.vue and MovieSearch.vue files inside the component folder
  • Replace the content of src/App.vue with the given below code.

Example: This example shows the creation of a Movie App.

JavaScript
//App.vue

<template>
  <div class="App">
    <header class="App-header">
      <img src=
"https://media.geeksforgeeks.org/gfg-gg-logo.svg"
      alt="GeeksforGeeks Logo"
      class="logo" />
      <h1>Movie Mania</h1>
    </header>
    <main>
      <MovieSearch @searchInput="searchInput"
                     @search="search" />
      <div class="container">
        <div
          v-for="movie in filteredResults"
          :key="movie.imdbID"
          class="item"
          @click="openDetail(movie.imdbID)"
        >
          <img :src="movie.Poster" alt="Movie Poster" />
          <h3>{{ movie.Title }}</h3>
        </div>
      </div>
      <MovieDetail v-if="selected.Title"
                     :selected="selected" 
                     @closeDetail="closeDetail" />
    </main>
  </div>
</template>

<script>
import axios from "axios";
import MovieSearch from "./components/MovieSearch.vue";
import MovieDetail from "./components/MovieDetail.vue";

export default {
  name: "App",
  components: {
    MovieSearch,
    MovieDetail,
  },
  data() {
    return {
      s: "", // Initialize search query
      results: [], // Initialize results array
      selected: {}, // Initialize selected movie object
      apiurl: "https://www.omdbapi.com/?apikey=a2526df0",
    };
  },
  mounted() {
    // Fetch initial movies based on specific keywords
    this.fetchInitialMovies();
  },
  methods: {
    fetchInitialMovies() {
      // Keywords for initial movie search
      const keywords = ["hindi", "avengers", "comedy"];
      
      // Fetch movies for each keyword 
      // and combine the results
      Promise.all(keywords.map(keyword => {
        return axios(this.apiurl + "&s=" + keyword)
          .then(({ data }) => data.Search || [])
          .catch(error => {
            console.error("Error fetching movies:", error);
            return [];
          });
      })).then(results => {
        this.results = results.flat(); 
        // Combine arrays of movies
      });
    },
    searchInput(e) {
      this.s = e.target.value;
    },
    search(e) {
      if (e.key === "Enter") {
        axios(this.apiurl + "&s=" + this.s)
             .then(({ data }) => {
          this.results = data.Search || [];
        });
      }
    },
    openDetail(id) {
      axios(this.apiurl + "&i=" + id).then(({ data }) => {
        this.selected = data;
      });
    },
    closeDetail() {
      this.selected = {};
    },
  },
  computed: {
    filteredResults() {
      // Filter results based on search query
      return this.results.filter(movie => movie.Title.toLowerCase()
                                  .includes(this.s.toLowerCase()));
    }
  },
};
</script>



<style>
.App {
  text-align: center;
  background-color: #f4f4f4;
  min-height: 100vh;
}

.App-header {
  background-color: #333;
  color: white;
  padding: 20px 0;
  display: flex;
  justify-content: center;
  align-items: center;
}

.logo {
  width: 50px;
  margin-right: 10px;
}

.container {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 20px;
  padding: 20px;
}

.item {
  cursor: pointer;
  width: 200px;
  text-align: center;
  padding: 10px;
  background-color: #fff;
  border-radius: 8px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  transition: transform 0.3s ease;
}

.item:hover {
  transform: translateY(-5px);
}

.item img {
  width: 100%;
  border-radius: 8px;
}

.MovieDetail {
  background-color: rgba(0, 0, 0, 0.8);
  color: white;
  padding: 20px;
  border-radius: 8px;
  max-width: 600px;
  margin: 20px auto;
}

.MovieDetail h2 {
  margin-top: 0;
}

.MovieDetail span {
  font-weight: bold;
}

.MovieDetail img {
  max-width: 100%;
  border-radius: 8px;
  margin-bottom: 20px;
}

.MovieDetail p {
  line-height: 1.5;
}

.close {
  background-color: #333;
  color: white;
  border: none;
  padding: 10px 20px;
  border-radius: 4px;
  cursor: pointer;
  transition: background-color 0.3s ease;
}

.close:hover {
  background-color: #555;
}

.search-bar {
  margin: 20px auto;
  max-width: 400px;
}

.search {
  width: 100%;
  padding: 10px;
  font-size: 16px;
  border: 1px solid #ddd;
  border-radius: 4px;
  box-sizing: border-box;
  transition: border-color 0.3s ease;
}

.search:focus {
  outline: none;
  border-color: #333;
}
</style>
JavaScript
//MovieSearch.vue

<template>
  <div class="search-bar">
    <input
      type="text"
      placeholder="Search for a Movie..."
      class="search"
      @input="searchInput"
      @keypress.enter="search"
    />
  </div>
</template>

<script>
export default {
  name: "MovieSearch",
  methods: {
    searchInput(e) {
      this.$emit("searchInput", e);
    },
    search(e) {
      this.$emit("search", e);
    },
  },
};
</script>

<style scoped>
.search-bar {
  margin: 20px auto;
  max-width: 400px;
}

.search {
  width: 100%;
  padding: 10px;
  font-size: 16px;
  border: 1px solid #ddd;
  border-radius: 4px;
  box-sizing: border-box;
  transition: border-color 0.3s ease;
}

.search:focus {
  outline: none;
  border-color: #333;
}
</style>
JavaScript
//MovieDetail.vue

<template>
  <div class="modal" 
         @click.self="closeDetail">
    <div class="modal-content">
      <span class="close"
              @click="closeDetail">&times;</span>
      <h2>{{ selected.Title }}</h2>
      <span>{{ selected.Year }}</span>
      <p class="rating">Rating: 
                  {{ selected.imdbRating }}</p>
      <div class="about">
        <img :src="selected.Poster" alt="Movie Poster" />
        <p>{{ selected.Plot }}</p>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: "MovieDetail",
  props: {
    selected: Object,
  },
  methods: {
    closeDetail() {
      this.$emit("closeDetail");
    },
  },
};
</script>

<style scoped>
.modal {
  display: flex;
  align-items: center;
  justify-content: center;
  position: fixed;
  z-index: 999;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.5);
}

.modal-content {
  background-color: #fefefe;
  border-radius: 8px;
  padding: 20px;
  max-width: 600px;
  max-height: 80vh; 
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}

.close {
  position: absolute;
  top: 10px;
  right: 10px;
  font-size: 20px;
  cursor: pointer;
}
</style>


Run the Project:

npm run dev

Output:


Next Article

Similar Reads