Build a Movie APP Using Next.js
Last Updated : 09 Aug, 2024
We will create a movie database app using Next.js and an external movie API (such as The Movie Database API). The app will enable users to view movies, search for specific movies, and view detailed information about each movie, including the title, release date, rating, brief description, and a list of actors and actresses.
Project Preview
Build a Movie Database Using Next.jsPrerequisites
Approach
- Set up a new Nextjs project with create-next-app.
- Create an api.js file to handle API requests.
- Define functions to fetch movie data, search movies, and fetch movie details with cast information.
- We will use Axios to fetch data from the TMDb API.
- Create Navbar.js for navigation links.
- Create HomePage.js to display movies lists and implement search functionality.
- Create dynamic route page [id].js to display detailed information and cast for a selected movie.
- Create pagination component It allows users to navigate between different pages of movie results.
Steps to Build a Movie Database with Next.js:
Step 1: Initialized the Nextjs app and installing the required packages
npx create-next-app@latest moviedb
Step 2: It will ask you some questions, so choose as the following.
√ Would you like to use TypeScript? ... No
√ Would you like to use ESLint? ... No
√ Would you like to use Tailwind CSS? ... No
√ Would you like to use `src/` directory? ... Yes
√ Would you like to use App Router? (recommended) ... Yes
√ Would you like to customize the default import alias (@/*)? ... No
Step 3: Install the necessary package for your project using the following command.
npm i axios bootstrap
Project Structure
Project StructureDependencies
"dependencies": {
"autoprefixer": "^10.4.19",
"bootstrap": "^5.3.3",
"next": "14.1.3",
"react": "^18",
"react-dom": "^18",
}
Step 4: Create the required files and write the following code to run this movie database app .
Example: We will create a Movie Database using next.js which allow users to view movies, search for specific ones, and access detailed information.
JavaScript // Page.js 'use client' import 'bootstrap/dist/css/bootstrap.min.css'; import HomePage from './components/HomePage'; function App() { return ( <> <HomePage/ > </> ); } export default App;
JavaScript // HomePage.js import React, { useEffect, useState } from 'react'; import { api } from '../api/api'; // Adjust import path as needed import MovieCard from './MovieCard'; import Pagination from './Pagination'; // Import the Pagination component import Navbar from './Navbar'; const HomePage = () => { const [movies, setMovies] = useState([]); const [currentPage, setCurrentPage] = useState(1); const [totalPages, setTotalPages] = useState(1); const [query, setQuery] = useState(''); const [searching, setSearching] = useState(false); useEffect(() => { const fetchMovies = async () => { try { const endpoint = searching ? '/search/movie' : '/movie/popular'; const response = await api.get(endpoint, { params: { page: currentPage, query: searching ? query : undefined } }); setMovies(response.data.results); setTotalPages(response.data.total_pages); } catch (error) { console.error(error); } }; fetchMovies(); }, [currentPage, query, searching]); const handleSearch = (e) => { e.preventDefault(); setCurrentPage(1); // Reset to first page for new search setSearching(true); // Set searching state to true }; const handleReset = () => { setQuery(''); setSearching(false); // Reset search state setCurrentPage(1); // Reset to first page }; return ( <> <Navbar /> <div className="container my-4"> <div className="d-flex justify-content-between mb-4"> <form onSubmit={handleSearch} className="d-flex"> <input className="form-control me-2" type="search" value={query} onChange={(e) => setQuery(e.target.value)} placeholder="Search for movies..." /> <button className="btn btn-outline-success" type="submit"> Search </button> </form> </div> {searching && ( <button onClick={handleReset} className="btn btn-outline-primary mb-4"> Reset Search </button> )} <div className="row"> {movies.map(movie => ( <div key={movie.id} className="col-12 col-sm-6 col-md-4 col-lg-3 mb-4"> <MovieCard movie={movie} /> </div> ))} </div> <Pagination currentPage={currentPage} totalPages={totalPages} onPageChange={(page) => setCurrentPage(page)} /> </div> </> ); }; export default HomePage;
JavaScript // MovieCard.js import React from 'react'; import Link from 'next/link'; import { IMAGE_BASE_URL } from '../api/api.js'; const MovieCard = ({ movie }) => { return ( <div className="card shadow-sm mb-4 bg-gray"> <Link href={`/movie/${movie.id}`} passHref> <div className="position-relative"> <img src={`${IMAGE_BASE_URL}${movie.poster_path}`} alt={movie.title} className="card-img-top" /> <div className="position-absolute top-0 start-0 m-2 bg-danger text-white text-xs font-weight-bold py-1 px-2 rounded"> {new Date(movie.release_date).getFullYear()} </div> </div> </Link> <div className="card-body"> <h5 className="card-title">{movie.title}</h5> <div className="d-flex justify-content-between align-items-center"> <span className="bg-warning text-dark text-xs font-weight-bold px-2 py-1 rounded"> ★ {movie.vote_average} </span> <span className="text-secondary text-xs font-weight-bold"> Popularity: {movie.popularity} </span> </div> </div> </div> ); }; export default MovieCard;
JavaScript // Navbar.js import React, { useState } from 'react'; import Link from 'next/link'; const Navbar = () => { const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); return ( <nav className="navbar navbar-expand-md navbar-dark bg-dark sticky-top"> <div className="container"> <Link href="/" passHref> <div className="navbar-brand" style={{ textDecoration: 'none', color: 'white' }}> MovieDB </div> </Link> <button className="navbar-toggler" type="button" onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)} aria-controls="navbarNav" aria-expanded={isMobileMenuOpen} aria-label="Toggle navigation" > <span className="navbar-toggler-icon"></span> </button> </div> </nav> ); }; export default Navbar;
JavaScript // Pagination.js import React from 'react'; const Pagination = ({ currentPage, totalPages, onPageChange }) => { const maxButtons = 5; // Calculate the start and end of the visible page range const startPage = Math.max(1, currentPage - Math.floor(maxButtons / 2)); const endPage = Math.min(totalPages, startPage + maxButtons - 1); // Adjust the start page if the end page is less than the max number of buttons const adjustedStartPage = Math.max(1, endPage - maxButtons + 1); const pages = Array.from({ length: endPage - adjustedStartPage + 1 }, (_, i) => adjustedStartPage + i); return ( <nav aria-label="Page navigation"> <ul className="pagination justify-content-center"> <li className={`page-item ${currentPage === 1 ? 'disabled' : ''}`}> <button className="page-link" onClick={() => onPageChange(currentPage - 1)} disabled={currentPage === 1} > « </button> </li> {pages.length > 0 && pages.map(page => ( <li key={page} className={`page-item ${currentPage === page ? 'active' : ''}`} > <button className="page-link" onClick={() => onPageChange(page)} > {page} </button> </li> ))} <li className={`page-item ${currentPage === totalPages ? 'disabled' : ''}`}> <button className="page-link" onClick={() => onPageChange(currentPage + 1)} disabled={currentPage === totalPages} > » </button> </li> </ul> </nav> ); }; export default Pagination;
JavaScript import React, { useEffect, useState } from 'react'; import { api, IMAGE_BASE_URL } from '@/app/api/api'; import { useRouter } from 'next/router'; import Link from 'next/link'; import 'bootstrap/dist/css/bootstrap.min.css'; import Navbar from '@/app/components/Navbar'; const MovieDetailPage = () => { const router = useRouter(); const { id } = router.query; const [movie, setMovie] = useState(null); const [cast, setCast] = useState([]); useEffect(() => { if (!id) return; // Fetch movie details api.get(`/movie/${id}`) .then(response => setMovie(response.data)) .catch(error => console.error ('Error fetching movie details:', error)); // Fetch movie cast api.get(`/movie/${id}/credits`) .then(response => { console.log('Cast response data:', response.data); setCast(response.data.cast); }) .catch(error => console.error('Error fetching movie cast:', error)); }, [id]); if (!movie) return <div className="text-center text-white mt-10">Loading...</div>; return ( <> <Navbar/> <div className="container my-4 text-white"> <Link href="/" passHref> <p className="text-primary mb-4">← Back to List</p> </Link> <div className="row mb-4 border-bottom border-light pb-4"> <div className="col-md-4 mb-3 mb-md-0"> <img src={`${IMAGE_BASE_URL}${movie.poster_path}`} alt={movie.title} className="img-fluid rounded shadow-lg" onError={(e) => e.target.src = 'https://media.geeksforgeeks.org/ wp-content/uploads/20240805102426/pta.jpg'} /> </div> <div className="col-md-8"> <h1 className="display-4 mb-2">{movie.title}</h1> <div className="mb-4"> <span className="badge bg-warning text-dark mr-2">Rating: {movie.vote_average}</span> <span className="mr-2">{movie.runtime} min</span> <span>{movie.genres.map(genre => genre.name).join(', ')}</span> </div> <p className="mb-2 text-dark"><strong>Release Date:</strong> {new Date(movie.release_date).toLocaleDateString()}</p> <h2 className="h4 text-dark mb-2">Overview</h2> <p className='text-dark'>{movie.overview}</p> </div> </div> <h2 className="h4 mb-4 border-bottom border-light pb-2">Cast</h2> <div className="row"> {cast.map(member => ( <div key={member.cast_id} className="col-6 col-sm-4 col-md-3 col-lg-2 mb-4 d-flex flex-column align-items-center"> <div className="position-relative"> <img src={member.profile_path ? `${IMAGE_BASE_URL}$ {member.profile_path}` : 'https://media.geeksforgeeks.org/ wp-content/uploads/20240805102426/pta.jpg'} alt={member.name} className="img-fluid rounded-circle" style={{ width: '120px', height: '120px', objectFit: 'cover' }} onError={(e) => e.target.src = 'https://media.geeksforgeeks.org /wp-content/uploads/20240805102426/pta.jpg'} /> </div> <p className="font-weight-bold text-center mt-2">{member.name}</p> <p className="text-muted text-center">{member.character}</p> </div> ))} </div> </div> </> ); }; export default MovieDetailPage;
JavaScript // src/api/api.js import axios from 'axios'; const API_KEY = 'Your_API_KEY'; const BASE_URL = 'https://api.themoviedb.org/3'; const IMAGE_BASE_URL = 'https://image.tmdb.org/t/p/w500'; const api = axios.create({ baseURL: BASE_URL, params: { api_key: API_KEY, language: 'en-US' } }); export { api, IMAGE_BASE_URL };
Step 5: Save all files and start the server by using the command.
npm run dev
Output: After running the application this output will be visible on http://localhost:3000/
Similar Reads
Build a Movie APP Using Next.js
We will create a movie database app using Next.js and an external movie API (such as The Movie Database API). The app will enable users to view movies, search for specific movies, and view detailed information about each movie, including the title, release date, rating, brief description, and a list
7 min read
Build a Task Management App using Next JS
A Task management app is a useful web application that assists in efficiently organizing and managing tasks. It provides various functionalities such as creating tasks, assigning prioritieÂs and deadlines, marking complete tasks, and enabling task search based on keÂywords and priorities. Preview of
5 min read
Build a Dictionary App Using Next Js
In this article, we'll create a dictionary app using Next Js. The application allows useÂrs to effortlessly search for word deÂfinitions, parts of speech, and eÂxample. By utilizing an online dictionary API, the app fetcheÂs relevant word data and organizes it in a structureÂd manner. Users can also
4 min read
Build a News Aggregator Using Next.js
In this article, weâll walk through the step-by-step process of creating a news aggregator application using Next.js. It gathers news articles from multiple sources and presents them in a unified interface. This project will fetch news data using the News API and display it in a structured format. P
3 min read
Build a Recipe Manager Using Next.js
A Recipe Manager is a web application that allows users to manage and share their recipes. This application enables users to add new recipes, view a list of recipes, manage recipe and search for recipes by title. Built with Next.js, this app leverages server-side rendering for better performance. Us
10 min read
Build a Notes App with Next.js
Note-taking App is a simple web application that allows users to create, edit, and delete text notes. The app provides a user-friendly interface for managing notes, making it easy to add new notes, update existing notes, and delete notes when they are no longer needed. The app provides a way for use
4 min read
Build a News Portal Using Next.js
In this article, we will explore how to build a news portal using Next.js, a popular React framework for server-side rendering and static site generation. The news portal will allow users to publish and categorize news articles, providing a seamless user experience with fast page loads and dynamic c
8 min read
Build a Movie App with VueJS
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:Vue.jsNode.js and npm are installed on your system.Approach Vu
5 min read
Build a Photo Sharing App Using Next.js
We will build a photo-sharing app using Next.js. This app will allow users to upload and share photos with a community. We will use Bootstrap for styling and localStorage to store the photos. The application will consist of two main pages: a home page to display the uploaded photos and an upload pag
4 min read
Build a Loan Calculator using Next.js
The Loan Calculator allows users to determine loan amount, interest rate, and tenure, calculate monthly mortgage payments, total loan payments over tenure, and total interest payable. In this article, we will walk you through the process of building a Loan Calculator using Next.js. Let's take a look
4 min read