Movie search application using Vue JS and Laravel framework using OMDb API – The Open Movie Database

A laravel movie search application using Vue JS, Vue Composition API and OMDBAI API. OMDBAI is used to fetch the movies data from https://www.omdbapi.com/

Requirements

Composer 2.0.13**
Node version v14.17.0**
PHP version 8.0.3** 

Installation

composer create-project laravel/laravel movie-search
cd movie-search
composer require laravel/ui
php artisan ui vue
npm install vue-loader@^15.9.5 --save-dev --legacy-peer-deps
npm install @vue/composition-api
npm install -D @babel/core @babel/preset-env babel-loader
npm install
npm run dev
npm run watch
You will need to gety API key from the OMDb API and update in /resources/js/components/movieapi/movie-api.js file

import { reactive, watch } from '@vue/composition-api';
const API_KEY = '######';

Once you are done with the above steps, now its time to start building the movie search application

In the Routes folder lets create a route for listing movies

//routes/web.php

Route::get('/', function () {
    return view('movielist');
});

Now create a blade template to list the vue js components which we will be creating at later stage. We will be create an app, header. search and movie component for this application.

//resources/views/movielist.blade.php

@extends('layouts.app')
@section('content')
    <div class="container">
        <app-component></app-component>
        <header-component></header-component>
        <search-component></search-component>
        <movie-component></movie-component>
    </div>    
@endsection

In the app.js include vue composition api which we will be using to build the vue components.

 //resources/js/app.js

 import VueCompositionApi from '@vue/composition-api';
 Vue.use(VueCompositionApi);

Now create movieapi to fetch data from the omdbapi.com and update API_KEY which you can get from the omdbapi website.

//resources/js/movieapi/movieapi.js

import { reactive, watch } from '@vue/composition-api';
const API_KEY = '######'; 

export const useMovieApi = () => {
    const state = reactive({
        search: 'Lord of the rings',
        loading: true,
        movies: []
    });

    watch(() => {
        const MOVIE_API_URL = `https://www.omdbapi.com/?s=${state.search}&apikey=${API_KEY}`;
        fetch(MOVIE_API_URL)
          .then(response => response.json())
            .then(jsonResponse => {
                state.movies = jsonResponse.Search;
                state.loading = false;
            });
    });
    return state;
};

Now create a Header component as below resources/js/components/HeaderComponent.vue

This will create a header with the title on the main movie search listing.


<template>
<div class="header">
    <header className="header mt-5 rounded">
        <h1>{{ titleText }}</h1>
    </header>
</div>
</template>

<script>
export default {
    name: 'Header',
    props: ['title'],
    setup({title}) {
        return {titleText: title};
    }
}
</script>

Search component will do movie search by movie title – resources/js/components/SearchComponent.vue

<template>
    <form class="search">
        <input
            type="text"
            :value="movieTitle"
            @keyup="handleChange"
        />
        <input id="searchfor"  @click="handleSubmit" type="submit" value="SEARCH" />
    </form>
</template>

<script>
import { ref } from '@vue/composition-api';

export default {
    name: 'Search',
    props: ['search'],
    setup({ search }, { emit }) {
        const movieTitle = ref(search);
        return {
            movieTitle,
            handleSubmit(event) {
                event.preventDefault();
                emit('search', movieTitle.value);
            },
            handleChange(event) {
                movieTitle.value = event.target.value
            }
        }
    }
}
</script>

Movie component will display movie with the Poster image, Movie Title, Year, Movie ID and Movie type – resources/js/components/MovieComponent.vue

<template>

    <div class="movie border border-secondary rounded" >
      <h2>{{ movie.Title }}</h2>
        <div v-if="movie.Poster === 'N/A'">
           <p>Image not available</p>
        </div>   
        <div v-else> 
            <img class="rounded" width="200" :alt="altText" :src="movie.Poster" />
        </div>
        <p class="font-weight-bold mt-3">Year: {{ movie.Year }}</p>
            <p class="font-weight-bold mt-3">Movie Id: {{ movie.imdbID }}</p>
         <p class="font-weight-bold mt-3">Type: {{ movie.Type }}</p>
    </div>
</template>

<script>
import { computed } from '@vue/composition-api';
    
export default {
    name: "Movie",
    props: ['movie'],
    setup({ movie }) {
        const altText = computed(() => `The movie title is: ${movie.Title}`);
        return { altText };
    }
};

</script>

App component will do the movie search and include all components to display the results – resources/js/components/AppComponent.vue

<template>
  <div class="text-center mt-3 border rounded">
    <Header :title="'Search for a Movie'" />
    <Search :search="state.search" @search="handleSearch" />
    <div class="movies m-5">
      <Movie v-for="movie in state.movies" :movie="movie" :key="movie.imdbID" />
    </div>
  </div>
</template>

<script>
import Header from './HeaderComponent.vue';
import Search from './SearchComponent.vue';
import Movie from './MovieComponent.vue';
import { useMovieApi } from '../movieapi/movie-api';

export default {
    name: 'app',
    components: { Header, Search, Movie },
    setup() {
        const state = useMovieApi();
        return {
            state,
            handleSearch(searchTerm) {
                state.loading = true;
                state.search = searchTerm;
            }
        };
    }
}
</script>

<style>

* {
    box-sizing: border-box;
}

.header {
    background-color: #165365;
    height: 3em;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    font-size: calc(10px + 2vmin);
    color: white;
    padding: 20px;
    cursor: pointer;
    border-radius: 5px;
}

.movies {
    display: flex;
    flex-wrap: wrap;
    flex-direction: row;
}

.movie {
    padding: 5px 25px 5px 25px;
    max-width: 25%;
}

.search {
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    justify-content: center;
    margin-top: 20px;
}

h2 {
    font-size: 22px;
}
input[type="submit"] {
    padding: 5px;
    background-color: #ee372c;
    color: #fff;
    border: 1px solid #ee372c;
    border-radius: 10px;
    width: 80px;
    margin-left: 5px;
    cursor: pointer;
}

input[type="submit"]:hover {
    background-color: #165365;
    border: 1px solid #165365;
    border-radius: 10px;
    color: #fff;
}

.search > input[type="text"]{
    width: 40%;
    min-width: 170px;
}

@media screen and (min-width: 694px) and (max-width: 915px) {
    .movie {
        max-width: 33%;
    }
}

@media screen and (min-width: 652px) and (max-width: 693px) {
    .movie {
        max-width: 50%;
    }
}

@media screen and (max-width: 651px) {
    .movie {
        max-width: 100%;
        margin: auto;
    }
}
</style>

Once this is, register vue components in the resources/js/app.js


 Vue.component('app-component', require('./components/AppComponent.vue').default);
 Vue.component('header-component', require('./components/HeaderComponent.vue'));
 Vue.component('search-component', require('./components/SearchComponent.vue'));
 Vue.component('movie-component', require('./components/MovieComponent.vue'));
 

After these changes run the laravel server with php artisan serve command and you should be able to see results as below:

Desktop View

Movie Search Vue Laravel

Mobile View

If you would like to download the full code for the movie search application it can be downloaded from Github

If you see any issues or have suggestions to improve this code, feel free to comment or get in touch.