DEV Community

Cover image for How to build a movie application using Next.js and Appwrite ?
Karthikeyan
Karthikeyan

Posted on • Originally published at imkarthikeyans.hashnode.dev

How to build a movie application using Next.js and Appwrite ?

In this tutorial we are going to build a movie application using Next.js , Appwrite, tailwind css, Digital Ocean and deploy it to Vercel.

Before going into coding. Let’s see a few details on the frameworks which we are going to work with.

Introduction:

Next.js: It is a React framework for building server side applications with more features like static rendering , Page based routing, smart bundling etc.

Appwrite: It is a self-hosted backend-as-a-service platform that provides developers with all the core APIs required to build any application.

Tailwindcss: Tailwind CSS is a highly customizable, low-level CSS framework that gives you all of the building blocks you need to build bespoke designs without any annoying opinionated styles you have to fight to override.

https://media.giphy.com/media/xT8qBsOjMOcdeGJIU8/giphy.gif

Appwrite Setup:

First we need to setup Appwrite to use its back-end features. There are two ways which you can do it.

  1. Docker ( Manual Configuration )
  2. Digital Ocean droplets ( pre-configured )

I will be trying out both ways but , In this tutorial we will be going with the second way.

Note : There will be article on how to manually configure Appwrite using docker

Pre-requisite : You need to have digitalocean account

Click on this link to create app-write droplet.

Once you get to the on-boarding screen, choose a basic plan ( Shared CPU ) and for CPU options we will go with minimum requirements. Then create a SSH key and keep it safe. Once the above things are done click on create a droplet.

description

Head over to this link and wait for sometime so that the docker images are installed in the droplet.

description

Click on the get started link which will open a side navigation modal. Click on Quick access to Appwrite which will take you the Appwrite console. It will look something like this.

Note: If you have a custom domain you can also link it to the digital ocean droplet. Once that is done , Appwrite will automatically take care of installing the SSL certificates for that domain.

description

Now with console setup is done, you will have to create your account.

Once you have created your account, click on Create Project. We will name the project as Movie app.

Now we need to create a platform. Click on Add Platform and click on New Web App. Give the name of the web-app as Movie-web and hostname as localhost and click on Register.

Finally, the setup complete.

https://media.giphy.com/media/fUSrPZImoDEpSN9XIY/giphy.gif

Let's create collection and add bunch of attributes.

Click on the database in the navigation menu on the left side. Click on Add Collection and create a new collection called Movies.

description

Now, head over to the collection and go to the Attributes tab and create the following attributes

description

Level 1 complete i,.e now our back end powered by Appwrite is ready to accept some data.

Nice work so far 👏 👏 👏.

https://media.giphy.com/media/M8dw7l8jhQvSAVpawW/giphy.gif

Before setting up the front end , one small step we need to do that is getting the API key from the TMDB.

Head over the TMDB site and create your account. Once done go to this link and create your API Key. Keep it safe , we will needing them when we code the API.

Front end setup

Head over to the terminal and run the following command. This will create a boilerplate app which will contain the basic Next.js and tailwindcss code.

npx create-next-app --example with-tailwindcss nextjs-movie-appwrite

# install the deps
yarn install

# run the app locally
yarn dev
Enter fullscreen mode Exit fullscreen mode

Now create your .env.local file at the root of your application and paste in the following

APPWRITE_ENDPOINT=https://[HOSTNAME_OR_IP]/v1
APPWRITE_PROJECT_ID=
APPWRITE_SERVER_API_KEY=
APPWRITE_COLLECTION_ID=
TMDB_MOVIE_KEY=
Enter fullscreen mode Exit fullscreen mode

For APPWRITE_ENDPOINT, since we are using digital ocean’s pre-configured Appwrite droplet you will be seeing a IP address in your url bar.

You can find the APPWRITE_PROJECT_ID in your console settings.

description

For getting the COLLECTION_ID , head over to database/collections/settings to find the collection id.

description

We will be using Appwrite’s Server side SDK for our application. Let’s first install the dependency

Go to the terminal and type in the following command

yarn add node-appwrite axios uuid
Enter fullscreen mode Exit fullscreen mode

Seeding the database:

Let’s create library function which will initialise the Appwrite SDK.

Create a new file called appwrite.ts under the folder lib and paste in the following code


import Appwrite from 'node-appwrite'

export const initAppwrite = () => {
  const sdk = new Appwrite.Client()
  sdk
    .setEndpoint(process.env.APPWRITE_ENDPOINT as string) // Your API Endpoint
    .setProject(process.env.APPWRITE_PROJECT_ID as string) // Your project ID;
    .setKey(process.env.APPWRITE_SERVER_API_KEY as string)

  return sdk
}


Enter fullscreen mode Exit fullscreen mode

How do you get the Server API Key ?

Go to the Appwrite console and click on the API keys

description

Create a new API key with the following scopes

collections.read
collections.write
document.read
document.write
Enter fullscreen mode Exit fullscreen mode

description

Go to pages/api and create a new file seed.ts and add in the following code

// Next.js API route support: https://nextjs.org/docs/api-routes/introduction


import type { NextApiRequest, NextApiResponse } from 'next'
import Appwrite from 'node-appwrite'
import axios from 'axios'
import { v4 as uuidv4 } from 'uuid'
import { initAppwrite } from '../../lib/appwrite'

type Data = {
  data: string
}

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse<Data>
) {
  let sdk = initAppwrite()
  let database = new Appwrite.Database(sdk)
  const response = await axios(
    `https://api.themoviedb.org/3/discover/movie?api_key=${process.env.TMDB_MOVIE_KEY}&language=en-US&sort_by=popularity.desc&include_adult=false&include_video=false&page=${req.query.page}`
  )
  const data = await response.data

  try {
    for (let i = 0; i < data.results.length; i++) {
      const item = data.results[i]
     let promise = await database.createDocument(
        process.env.APPWRITE_COLLECTION_ID as string,
        uuidv4(),
        {
          movie_id: item.id,
          title: item.title,
          thumbnail_image: `https://image.tmdb.org/t/p/original/${item.poster_path}`,
          popularity: item.popularity,
          release_date: item.release_date,
          vote_average: item.vote_average,
        }
      )

      console.log(promise)
    }
    res.status(200).json({ data: 'OK' })
  } catch (e) {
    console.log(e)
    res.status(500).json({ data: 'NOT OK' })
  }
}
Enter fullscreen mode Exit fullscreen mode

Code walkthrough:

First, we initialise the sdk with help of the library function initAppwrite which gives us the instance of the sdk which we can use to create collections , documents etc.

In the API, we will get the page number as the query param which will be used, when fetching the movies from the TMDB API.

Once we get the response , we loop through movie results and create a document using database.createDocument method provided by Appwrite.

**database.createDocument** will take 3 arguments which is collection id , document id ( which we generate with help of the uuid package to give us a unique id for each document ) and finally the details for each document.

Now head over to the browser and type in the following url. Make sure your server is running.

http://localhost:4040/api/seed?page=10
Enter fullscreen mode Exit fullscreen mode

If everything goes as expected , you should see a JSON response OK on your browser, alternatively you can head over to the Appwrite console and go to the collection to check if those documents are added.

Let’s do that.

Your browser should show something like this

adding to the appwrite database as a

description

Now our DB is successfully seeded with bunch of movie data.

let's get those movie from the database and display it on our application's homepage

Rendering movies in the Home page:

Let's create our layout component, so that every time we create our page we don't have to rewrite the same thing over and over again.

Create a components folder, inside that create layout.tsx file and paste in the following content.


import Link from 'next/link'

const Layout = ({ children }: any) => {
  return (
    <>
      <header className="body-font text-gray-600">
        <div className="container mx-auto flex flex-col flex-wrap items-center p-8 md:flex-row">
          <Link href="/">
            <span className="title-font mb-4 flex cursor-pointer items-center text-2xl font-medium text-gray-900 md:mb-0">
              Mflix
            </span>
          </Link>
          <nav className="flex flex-wrap items-center justify-center text-base md:mr-auto   md:ml-4 md:border-l md:border-gray-400 md:py-1 md:pl-4">
            <span className="mr-5 hover:text-gray-900">
              <Link href="/add">Add Movie</Link>
            </span>
            <span className="mr-5 hover:text-gray-900">
              <Link href="/bookmark">Bookmarks</Link>
            </span>
          </nav>
        </div>
      </header>
      <div className="container mx-auto p-8">{children}</div>
    </>
  )
}

export default Layout

Enter fullscreen mode Exit fullscreen mode

Head over to your browser to see the layout changes.

description

We will create a API endpoint called getMovies and use them to fetch the data from the database.

In the API folder , create a file called getMovies.ts and add in the following content.


import { NextApiRequest, NextApiResponse } from 'next/types'
import Appwrite from 'node-appwrite'
import { initAppwrite } from '../../lib/appwrite';


export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {

    try{
        let sdk = initAppwrite();
        let database = new Appwrite.Database(sdk)

        const limit:any = req.query.limit;
        const offset:any = req.query.offset;

        let response = await database.listDocuments(process.env.APPWRITE_COLLECTION_ID as string, [],limit, offset)
        console.log(response.total)
        res.status(200).json({ data: response, count: response.total })

    }catch(e){
        res.status(500).json({data: e})
    }   


}


Enter fullscreen mode Exit fullscreen mode

Code Walkthrough

In the above code we made use of the [listDocuments](https://appwrite.io/docs/server/database?sdk=nodejs-default#databaseListDocuments) method provided by Appwrite.

We will be able to get 25 documents by default from the database, which is why we will be passing the values of limit and offset as query parameter while calling the API.

What is Offset ?

Offset in this context is basically when you tell the Appwrite server to skip the number of records before fetching.

That did not make sense right.

https://media.giphy.com/media/WRv4LT6GmtiABVpYNp/giphy.gif

For example, Let's say you want to go to page 2 , now you don't need to have the first 25 records , so you can mention in the offset param as 25 which will skip the first 25 records and get you the next 25 records to be displayed on the interface.

Let's write our UI code to call the API when the component mounts. In the pages folder create a file named index.tsx and add in the following content.


// index.tsx

import type { NextPage } from 'next'
import Link from 'next/link'
import { useEffect, useState } from 'react'
import Layout from '../components/layout'
import Pagination from '../components/pagination'


const Home: NextPage = () => {

  const [offset, setOffset] = useState(0);
  const [pageLimit] = useState(25)
  const [totalPageCount, setTotalPageCount] = useState(0)

  const fetchMovies = async (limit: any,offset: any) =>{
    const response = await fetch(`/api/getMovies?limit=${limit}&offset=${offset}`)
    const data = await response.json()
    setMovies(data.data.documents)
    setTotalPageCount(data.count)
  }

  useEffect(()=>{
    fetchMovies(pageLimit, offset)
  },[offset])

  const handleNextPage =  () => {
    setOffset((prevOffset) => prevOffset + pageLimit)
  }

  const handlePreviousPage = () => {
   if(offset > 0 ){
    setOffset((prevOffset) => {
      return prevOffset - pageLimit
    })
   } 
  }
  const [movies, setMovies] = useState([])

  return (
    <Layout>
      <section className="body-font text-gray-600 pb-4">
        <div className="mt-6 grid grid-cols-1 gap-16 sm:grid-cols-2 lg:grid-cols-4 xl:gap-x-20">
          {movies.map((movie: any) => {
            return (
              <a className="cursor-pointer">
                <Link href={`/movie/${movie.movie_id}`}>
                  <div key={movie.movie_id} className="group relative">
                    <div className="min-h-80 aspect-w-1 aspect-h-1 lg:aspect-none w-full overflow-hidden rounded-md bg-gray-200 group-hover:opacity-75 lg:h-80">
                      <img
                        src={movie.thumbnail_image}
                        alt={movie.title}
                        className="h-full w-full object-cover object-center lg:h-full lg:w-full"
                      />
                    </div>
                    <div className="mt-4 flex justify-between">
                      <div>
                        <h3 className="text-gray-700">

                          {movie.title}
                        </h3>
                        <p className="mt-1 text-sm text-gray-500">
                          {new Date(movie.release_date).toDateString()}
                        </p>
                      </div>
                      <p className="text-sm font-medium text-gray-900">
                        {movie.price}
                      </p>
                    </div>
                  </div>
                </Link>
              </a>
            )
          })}
        </div>
      </section>
      <Pagination 
        totalMovies={totalPageCount}
        postPerPage={pageLimit}
        nextOffset={offset + pageLimit}
        currentPage={0}
        handleNextPage={handleNextPage}
        handlePreviousPage={handlePreviousPage}
      />
    </Layout>
  )
}

export default Home

Enter fullscreen mode Exit fullscreen mode

Code walkthrough

First when the component mounts , we set three local variables offset, page limit and page count. Then the useEffect runs fetching the movies and setting up the movie data.

we use the same data to render them in the screen.

Note: I have used useEffect to do a data fetching in the client side. Alternatively , you can also do a server side data fetching using getServerSideProps.

In the pagination component we use the relevant props to navigate between the pages.

Inside the components folder, create a new file called Pagination.tsx and add in the following content.




const Pagination = (props: {
  totalMovies: number
  postPerPage: number
  currentPage: number
  handleNextPage: () => void
  nextOffset: number
  handlePreviousPage:  () => void
}) => {
  return (
    <div className="flex items-center justify-between border-t border-gray-200 bg-white px-4 py-3 sm:px-6">
      <div className="flex flex-1 justify-between">
        <button
          disabled={props.currentPage === 1}
          onClick={props.handlePreviousPage}
          className="relative inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50"
        >
          Previous
        </button>

        {props.nextOffset <= props.totalMovies && (
          <button
            onClick={props.handleNextPage}
            className="relative ml-3 inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50"
          >
            Next
          </button>
        )}
      </div>
    </div>
  )
}

export default Pagination


Enter fullscreen mode Exit fullscreen mode

Code walkthrough

In the pagination component, handlePreviousPage and handleNextPage will take care of the navigating between pages ( takes care of setting up the offset value accordingly ) and then we disable prev and next button based on the offset value.

Head over to the browser to see the magic

description

Adding movies to the Database via form :

We are going to add the movie to the collection via a form from the browser.

Fun part is, we won't have to add to movie name , year or upload the image. TMDB API has a way to fetch the information using IMDB id.

The IMDB id in the url https://www.imdb.com/title/tt4154756/?ref_=hm_tpks_tt_i_2_pd_tp1_cp is tt4154756. Once we input the ID in the form , it will fetch relevant movie information and check if it is already in the database , if not it will add them to the database.

Let's see how we can do that. First as usual ,let's create our API endpoint.

Go to api folder and create a new file called addMovies.ts and add in the following content


import type { NextApiRequest, NextApiResponse } from 'next'
import { initAppwrite } from '../../lib/appwrite'
import Appwrite, { Query } from 'node-appwrite'
import { v4 as uuidv4 } from 'uuid'

type Data = {
  data?: any,
  id?: number
}
export default async (req: NextApiRequest, res: NextApiResponse<Data>) => {
  try {
    const imdbID = req.query.imdbID
    const sdk = initAppwrite()
    let database = new Appwrite.Database(sdk)

    const response = await fetch(
      `https://api.themoviedb.org/3/find/${imdbID}?api_key=${process.env.TMDB_MOVIE_KEY}&language=en-US&external_source=imdb_id`
    )
    const data = await response.json()

    if (data.movie_results.length > 0) {

      const id = data.movie_results[0].id
      const item = data.movie_results[0]

      const movieInDatabase = await database.listDocuments(
        process.env.APPWRITE_COLLECTION_ID as string,
        [Query.equal('movie_id', id)]
      )

      if (movieInDatabase.documents.length > 0) {
        // there are movies present in the database;
        return res.status(409).json({ data: 'Movie already exists', id })
      } else {
        // add the movie to the database
        await database.createDocument(
          process.env.APPWRITE_COLLECTION_ID as string,
          uuidv4(),
          {
            movie_id: id,
            title: item.title,
            thumbnail_image: `https://image.tmdb.org/t/p/original/${item.poster_path}`,
            popularity: item.popularity,
            release_date: item.release_date,
            vote_average: item.vote_average,
          }
        )

        return res.status(200).json({id })
      }
    }else{
        console.log("it is not a movie")
    }
  } catch (e) {
    console.log(e)
    res.status(500).json({ data: e })
  }
}

Enter fullscreen mode Exit fullscreen mode

As usual , we initialise the Appwrite SDK and then fetch the movie details using TMDB api. We check if it is a TV show related IMDB id has been entered and then we check if the document is present in the collection.

If the document is found, then we send in the response saying movie already exists. If it is not found then we add it to the collection.

Now onto coding the component. Create a file called add.tsx in the pages directory and add in the following content


import { useRouter } from 'next/router'
import { useState } from 'react'
import Layout from '../components/layout'

const Add = () => {
  const [id, setImdbId] = useState('')
  const router = useRouter()
  const handleAddMovie = async () => {
    try {
      const response = await fetch(
        `/api/addMovie?imdbID=${id}`
      )
      const data = await response.json()
      if (response.status === 200 && data.id) {
        console.log(
          'Added the movie succesfully, redirecting to the movie page'
        )
        return router.push(`/movie/${data.id}`)
      }
      if(response.status === 409 && data.id){
        console.log(
            'Movie already exists, redirecting to the movie page'
          )
          return router.push(`/movie/${data.id}`)
      }
    } catch (e) {
      console.log(e)
    }
  }

  return (
    <Layout>
      <label className="flex flex-row space-x-2">
        <input
          type="text"
          className=" block w-1/2"
          placeholder="Enter IMDB ID"
          onChange={(e) => setImdbId(e.target.value)}
        />
        <button
          className="bg-black p-2 px-4 text-lg text-white"
          onClick={handleAddMovie}
        >
          Add Movie
        </button>
      </label>
    </Layout>
  )
}

export default Add


Enter fullscreen mode Exit fullscreen mode

In the component we get the IMDB id from the user and then we sent it to the API.

Based on the response we get from the API we create a movie and redirect it to separate movie page ( which we will be coding in the later part of this tutorial ).

description

Bookmarking the movie:

Let's code our last feature for the application, which is bookmarking the movie. We will first create our separate movie page and add in the bookmark. We will be using Next.js dynamic routes feature for this.

Create a folder called movie in the pages directory and inside that create a file called [id].tsx.

Now if we go to the url localhost:3000/movie/1 we can send the id present in the url to the API to get the particular movie detail.

Inside the [id].tsx file you created , add in the following content.

Note: We will be storing the bookmarked movies in the IndexDB. For that I am using a package called localforage.

Run this command to install the dependency

yarn add localforage


import axios from 'axios'
import localforage from 'localforage'
import { useEffect, useState } from 'react'
import Layout from '../../components/layout'

const Movie = (props: { movie: any }) => {
  const { movie } = props
  const [bookmarkStatus, setBookMarkedStatus] = useState(false);

  useEffect(()=>{
    const setBookMarkOnInit = async () => {
      const movies:any = await localforage.getItem('movies');
      if(movies && movies.length === 0){
        const filterMovie = movies.filter((data: any) => data.movie_id === movie.id);
        console.log(filterMovie)
        if(filterMovie.length  > 0){
          setBookMarkedStatus(true)
        }
      }

    }
    setBookMarkOnInit()
  },[])

  const handleBookmark = async () => {
    console.log("store the data in indexDB")
    const data = await localforage.getItem("movies");

    const movieDataToStore = {
      movie_id: movie.id,
      title: movie.title,
      thumbnail_image: `https://image.tmdb.org/t/p/original/${movie.poster_path}`,
      popularity: movie.popularity,
      release_date: movie.release_date,
      vote_average: movie.vote_average,
    }
    if(!data){
      localforage.setItem("movies", [movieDataToStore])
      setBookMarkedStatus(true)
    }else{
      const existingData:any = await localforage.getItem('movies');
      const filteredData:any = existingData.filter((data: { movie_id: any }) => movie.id === data.movie_id);
      console.log("movie exists" ,filteredData)
      if(filteredData.length === 0){
        // movie does not exists in the indexDB 
        localforage.setItem("movies", [...existingData, movieDataToStore])
        setBookMarkedStatus(!bookmarkStatus)
      }
    }
  }


  return (
    <>
      <Layout>
        <div className="flex flex-col space-y-8 items-center lg:items-start lg:flex-row lg:space-x-8 lg:space-y-0">
          <img
            src={`https://image.tmdb.org/t/p/original/${movie.poster_path}`}
            className="w-2/3 lg:w-1/4"
          />
          <div className="flex flex-col justify-items-start space-y-12">
            <div className="flex flex-col  items-center justify-between space-y-2 lg:space-y-4 lg:items-start">
              <h1 className="text-2xl font-bold text-center lg:text-left"> {movie.title} </h1>
              <div className="flex flex-shrink-1 items-center  pr-2 text-sm space-x-2">
                <div className="rounded-r bg-green-200 px-2 py-1 text-green-700">
                  <p> {movie.status} </p>
                </div>
                {movie.genres.map((genre: any) => {
                  return (
                    <div className="rounded-r bg-green-200 px-2 py-1 text-green-700 whitespace-nowrap">
                      <p> {genre.name} </p>
                    </div>
                  )
                })}
              </div>
            </div>
            <div className='text-center md:text-justify'>
              <p> {movie.overview} </p>
            </div>
            <div className="flex flex-col space-y-2 md:flex-row md:space-x-2 md:space-y-0">
              <a
                href={movie.homepage}
                target="_blank"
                className="flex items-center space-x-2   bg-black p-2 pl-5 pr-5 text-lg text-white "
              >
                <svg
                  className="h-6 w-6"
                  fill="currentColor"
                  viewBox="0 0 20 20"
                  xmlns="http://www.w3.org/2000/svg"
                >
                  <path d="M11 3a1 1 0 100 2h2.586l-6.293 6.293a1 1 0 101.414 1.414L15 6.414V9a1 1 0 102 0V4a1 1 0 00-1-1h-5z"></path>
                  <path d="M5 5a2 2 0 00-2 2v8a2 2 0 002 2h8a2 2 0 002-2v-3a1 1 0 10-2 0v3H5V7h3a1 1 0 000-2H5z"></path>
                </svg>
                <span>Movie Homepage</span>
              </a>
              <button className='flex items-center space-x-2   bg-black p-2 pl-5 pr-5 text-lg text-white ' onClick={handleBookmark}>
              <svg className="w-6 h-6" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M5 4a2 2 0 012-2h6a2 2 0 012 2v14l-5-2.5L5 18V4z"></path></svg>
              <span> {bookmarkStatus ? "Bookmarked" : "Bookmark"} </span>
              </button>
            </div>
          </div>
        </div>
      </Layout>
    </>
  )
}

export async function getServerSideProps(context: any) {
  const { id } = context.query

  const response = await axios(
    `https://api.themoviedb.org/3/movie/${id}?api_key=${process.env.TMDB_MOVIE_KEY}&language=en-US`
  )

  const data = await response.data
  return {
    props: {
      movie: data,
    },
  }
}

export default Movie


Enter fullscreen mode Exit fullscreen mode

Since we are storing the movie id in our database , we pass it dynamically when the user visits the separate movie page. We use that in the getServerSideprops method to fetch the particular movie data.

Since we are not storing the particular movie data in the database , we use the TMDB API to fetch the movie information.

When the component mounts, we need to check whether the movie has already been bookmarked or not. For that we use useEffect combined with localforage method to get the bookmarked status.

When the user clicks on the Bookmark button , we then add the movie to localstorage list.

When you visit your browser , you should see something like this

description

description

Now that we can bookmark the movies, we need a place to render them. Let's quickly create a page named bookmark.tsx and add in the following content.


import Layout from '../components/layout'
import { useEffect, useState } from 'react'
import localforage from 'localforage'
import Link from 'next/link'

const Bookmark = () => {
  const [bookmarkedMovies, setBookMarkedMovies] = useState([])
  useEffect(() => {
    const getMovieFromLocalStorage = async () => {
      const bookMarkedMovies: any = await localforage.getItem('movies')
      if (bookMarkedMovies && bookMarkedMovies.length !== 0) {
        setBookMarkedMovies(bookMarkedMovies)
      }
    }
    getMovieFromLocalStorage()
  })

  const renderMovies = () =>{
    if(bookmarkedMovies.length === 0){
      return(
        <> 
          <p> No Bookmarked movies found. Head over to <Link href={'/'}> Movies </Link></p>
        </>
      )
    }
    return(
      bookmarkedMovies.map((movie: any) => {
        return (
          <a className="cursor-pointer">
            <Link href={`/movie/${movie.movie_id}`}>
              <div key={movie.movie_id} className="group relative">
                <div className="min-h-80 aspect-w-1 aspect-h-1 lg:aspect-none w-full overflow-hidden rounded-md bg-gray-200 group-hover:opacity-75 lg:h-80">
                  <img
                    src={movie.thumbnail_image}
                    alt={movie.title}
                    className="h-full w-full object-cover object-center lg:h-full lg:w-full"
                  />
                </div>
                <div className="mt-4 flex justify-between">
                  <div>
                    <h3 className="text-gray-700">{movie.title}</h3>
                    <p className="mt-1 text-sm text-gray-500">
                      {new Date(movie.release_date).toDateString()}
                    </p>
                  </div>
                  <p className="text-sm font-medium text-gray-900">
                    {movie.price}
                  </p>
                </div>
              </div>
            </Link>
          </a>
        )
      })
    )
  }
  return (
    <Layout>
      <section className="body-font text-gray-600">
        <div className="mt-6 grid grid-cols-1 gap-16 sm:grid-cols-2 lg:grid-cols-4 xl:gap-x-20">
          {renderMovies()}
        </div>
      </section>
    </Layout>
  )
}

export default Bookmark


Enter fullscreen mode Exit fullscreen mode

In this component , we simply fetch the data from indexDB and render it on the screen.

You should see something like this in your browser.

description

That's it. You can deploy the finished code to vercel.

https://media.giphy.com/media/OoTKFwKiOAbYc/giphy.gif

Conclusion

That's pretty much it. Thank you for taking the time to read the tutorial. I hope , everyone understood how to create application using Appwrite and Nextjs.

If you found the post useful , add ❤️ to it and let me know if I have missed something or if you have any doubts in the code in the comments section.

Feedback on the tutorial is most welcome.

Repository Link: App write Movie

Deployed Link: appwrite-nextjs-movie.vercel.app

Digital Ocean Free $100 credits :(Not a compulsion to use this referral code )

DigitalOcean Referral Badge

Social Links:

Twitter
Showwcase

Top comments (8)

Collapse
 
celeron profile image
Khushal Bhardwaj

Please upload a clear an more zoomed image or provide the attribute list as text the current one is really hard on eyes and I couldn't get the last one.

Thanks for this article.

Collapse
 
imkarthikeyan profile image
Karthikeyan • Edited

More zoomed screenshot. Thanks for the feedback

https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ue82nhbc6er85nnzse13.png

Collapse
 
celeron profile image
Khushal Bhardwaj

Thanks for the quick response to my request, appreciate it ☺️

Thread Thread
 
imkarthikeyan profile image
Karthikeyan

Welcome

Collapse
 
just_moh_it profile image
Mohit Yadav

Nice... I didn't understand why you used Appwrite for the app, since the API is pretty self-sufficient. Thanks 😃

Collapse
 
imkarthikeyan profile image
Karthikeyan

Thank you. API is sufficient but mostly wanted to try out Appwrite's services and still issues can happen on the TMDB's server end, so I did not want to completely rely on the API to take care of the application, hence went with Appwrite.

Collapse
 
just_moh_it profile image
Mohit Yadav

Very thoughtful 🙂

Thread Thread
 
imkarthikeyan profile image
Karthikeyan

🙂