Create a movie rating app with Keystone.js
Tags: graphql, javascript, postgres, tutorial
Published: 2020-05-14 15:14:31

---
title: Create a movie rating app with Keystone.js
published: true
date: 2020-04-21 17:30:05 UTC
tags: graphql,javascript,postgresql,tutorial
canonical_url: https://blog.logrocket.com/create-a-movie-rating-app-with-keystone-js/
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/amap48iz83oc5ryy4gjw.png
---
**Written by [Michael Mulders](https://blog.logrocket.com/author/michielmulders/)**✏️

An interesting alternative to WordPress, [Keystone.js](https://www.keystonejs.com/) is a content management system (CMS) built with Node.js that uses [Express.js](https://expressjs.com/) for its backend and a MongoDB or PostgreSQL database as its storage layer. It’s flexible and enables you to customize your CMS while still maintaining a lightweight codebase, unless WordPress.

Keystone.js provides GraphQL support, which is pretty powerful. You can quickly define schemas and the GraphQL engine will take care of the integration with PostgreSQL or MongoDB.

Moreover, Keystone.js allows you to choose which underlying database you want to use. Natively, it supports both PostgreSQL and MongoDB, which gives you the ability to choose between a relational and nonrelational database. GraphQL will generate a set of useful queries related to CRUD operations so you don’t have to code those queries. It’s a great feature that saves you a lot of time.

Also, the Keystone Admin UI automatically changes based on the schema you define. All data can be created, updated, and deleted via the admin user interface. If you add a schema about books, for example, Keystone.js will generate a whole admin panel to manage your books. Another powerful feature that makes developers’ lives easier.

In this tutorial, we’ll demonstrate how to build a movie rating app with Keystone.js. You can download the full code for this project from this [GitHub repository](https://github.com/michielmulders/keystonejs-movie-rating-app).

[![LogRocket Free Trial Banner](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2017/03/f760c-1gpjapknnuyhu8esa3z0jga.png?resize=1200%2C280&ssl=1)](https://logrocket.com/signup/)

## Requirements

Before you [get started using Keystone.js](https://www.keystonejs.com/quick-start/#requirements), you’ll need the following. (Note: For this tutorial, we’ll use MongoDB).

- [Node.js](https://nodejs.org/) version 10.x or higher
- [Yarn](https://classic.yarnpkg.com/en/docs/install/#mac-stable) stable version
- [MongoDB](https://www.mongodb.com/) version 4.x or higher

Next, make sure your MongoDB instance is running. Once you have all the dependencies, it’s time to get started.

You can start with a Keystone.js template, such as a sample to-do app or an authentication example. However, for the purposes of this tutorial, we’ll start from scratch.

## Step 1: Project setup

First, create a new Keystone application using the `keystone-app` command. You can directly use this command with Yarn from your CLI.

```jsx
yarn create keystone-app movie-rating
```

You’ll be prompted to answer three questions:

1. What is the project name? Enter `movie-rating`
2. Which starter template do you want to use? Select `blank` to generate an empty template
3. Which adapter do you want to use? Select `Mongoose`.

![Create a New App With Keystone.js](https://i2.wp.com/blog.logrocket.com/wp-content/uploads/2020/04/create-new-app-keystone-1.png?resize=720%2C256&ssl=1)

The command will copy the right project files in a new folder called `movie-rating`. You’ll end up with the following application structure.

```jsx
/movie-rating
- /node_modules
- index.js
- package.json
- README.md
```

Now let’s create the data model for storing movie ratings.

## Step 2: Create the data model

In this step, we’ll create our data schema. Currently, our `index.js` file looks like the code snippet below. Since the `MongooseAdapter` has already been connected, we can focus on writing our schema.

```jsx
const { Keystone } = require('@keystonejs/keystone');
const { GraphQLApp } = require('@keystonejs/app-graphql');
const { AdminUIApp } = require('@keystonejs/app-admin-ui');
const { MongooseAdapter: Adapter } = require('@keystonejs/adapter-mongoose');

const PROJECT_NAME = "movie-rating";

const keystone = new Keystone({
  name: PROJECT_NAME,
  adapter: new Adapter(),
});

module.exports = {
  keystone,
  apps: [new GraphQLApp(), new AdminUIApp({ enableDefaultRoute: true })],
};
```

View the source code on [Gist](https://gist.github.com/michielmulders/00853641fb7fb56aab51cf8d77351bae).

First, we need to install the `@keystonejs/fields` dependency, which holds all the supported field types we need to define new fields in our schema.

Install this dependency via Yarn:

```jsx
yarn add @keystonejs/fields
```

Now that’s we’ve added this dependency to our project, we can import the required types, `Text` and `Integer`.

```jsx
const { Text, Integer } = require('@keystonejs/fields');
```

Now we can create the movie rating schema. The schema will consist of two properties: `title`, which accepts a `Text` type, and `rating`, which accepts an `Integer` type.

```jsx
keystone.createList('Movie', {
  fields: {
    title: { 
      type: Text,
      isRequired: true,
      isUnique: true
    },
    rating: { 
      type: Integer,
      isRequired: true,
      defaultValue: 10
    }
  },
});
```

You can add extra properties for each [field](https://www.keystonejs.com/keystonejs/fields/). For example, you can combine the `Integer` type with a `defaultValue `property. You can also use the `isUnique` property, which enforces inputs to be unique.

For this step, your code should look like this.

<iframe style="width: 100%; height: 500px; border: 0; border-radius: 4px; overflow: hidden;" title="green-bird-56t7x" src="https://codesandbox.io/embed/green-bird-56t7x?fontsize=14&amp;hidenavigation=1&amp;theme=dark" sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"></iframe>

## Step 3: Start your project and explore

Start the project with the following command.

```jsx
yarn run dev
```

This will spin up the following elements:

- Keystone Admin UI: [http://localhost:3000/admin](http://localhost:3000/admin)
- GraphQL Playground: [http://localhost:3000/admin/graphiql](http://localhost:3000/admin/graphiql)
- GraphQL API: [http://localhost:3000/admin/api](http://localhost:3000/admin/api)

First, open the admin UI at [http://localhost:3000/admin](http://localhost:3000/admin). You’ll see the newly created movie list.

![Keystone.js Movie Rating App Dashboard](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2020/04/movie-rating-app-dashboard-1.png?resize=720%2C250&ssl=1)

If you click the plus icon on the Movies card, you can add a new movie to the list. For example, let’s add “Interstellar” and assign it a rating of 8.

![Add Movie to Keystone.js Movie Rating App Dashboard](https://i2.wp.com/blog.logrocket.com/wp-content/uploads/2020/04/movie-rating-app-dashboard-add-movie-1.png?resize=720%2C319&ssl=1)

Hit the create button to store the record in your MongoDB instance. You’ll see an overview of your newly created record.

![Edit New Record in Keystone.js Movie Rating App](https://i2.wp.com/blog.logrocket.com/wp-content/uploads/2020/04/movie-rating-app-edit-new-record-1.png?resize=720%2C240&ssl=1)

Let’s try to add the same record again. If the `isUnique` property has been configured correctly, the admin UI should throw an error.

![GraphQL Error in Keystone.js Movie Rating App](https://i2.wp.com/blog.logrocket.com/wp-content/uploads/2020/04/movie-rating-app-graphql-error-1.png?resize=720%2C240&ssl=1)

### Power of GraphQL

Keystone.js will process each defined schema, such as the Movie schema. For each schema, it creates [GraphQL CRUD operations](https://www.keystonejs.com/guides/schema#lists) and associated queries. We can use all those queries to change or access data in MongoDB.

Below is an overview of the generated operations for the Movie schema.

```jsx
type Mutation {
  createMovie(..): Movie
  updateMovie(..): Movie
  deleteMovie(..): Movie
}

type Query {
  allMovies(..): [Movie]
  Movie(..): Movie // query single movie
  GetMovies(..): [Movie]
}

type Movie {
  id: ID
  title: String
  rating: Integer
}
```

For more about the [GraphQL Schema Definition Language (SDL)](https://graphql.org/learn/schema/), see the official website.

With the backend part completed, the next step is to create an interface to interact with the movie rating schema.

## Step 4: Create an interface

The next step is to build a simple static HTML website that allows you to interact with your data via the GraphQL API endpoint at [http://localhost:3000/admin/api](http://localhost:3000/admin/api).

To connect to a static page, add the `@keystonejs/app-static` dependency.

```jsx
yarn add @keystonejs/app-static 
```

Don’t forget to import the dependency at the top of the `index.js` file.

```jsx
const { StaticApp } = require('@keystonejs/app-static');
```

As you can see, Keystone.js defines the static page dependency as an application. This means we can add the `StaticApp` object to the apps array, which is exported at the bottom of the `index.js` file.

Notice how we configured the `StaticApp` object: we told the object to look for our static pages in the `public` folder, which we will create in the next step. This folder hosts the HTML file we’ll create.

```jsx
module.exports = {
  keystone,
  apps: [
    new GraphQLApp(), 
    new StaticApp({ path: '/', src: 'public' }),
    new AdminUIApp({ enableDefaultRoute: true })
  ],
};
```

Now let’s create the `public` folder in the root of the project.

```jsx
mkdir public
```

Next, create the following three files.

1. `index.html` — Holds all the HTML code
2. `styles.css` — Basic styling for our static website
3. `script.js` — Holds logic for interacting with GraphQL endpoint and loading data

Your project folder should look like this:

```jsx
/movie-rating
- /node_modules
- /public
- index.html
- styles.css
- script.js
- index.js
- package.json
- README.md
```

### Add styling

This isn’t an absolutely essential step, but it’s always nice to have a pretty interface. All you have to do is create a `styles.css` file with the below contents.

{% codesandbox 56t7x %}

### Add HTML

Add the HTML to the `index.html` file. Be sure to look at the `body` tag, where we define our `script` element. This script acts as a hook to all the logic we need to dynamically load data and fetch static HTML.

```jsx
<body>
    <script type="text/javascript" id="movie-app" src="/script.js"></script>
 </body>
```

Next, copy the following HTML contents into your `index.html` file.

{% codesandbox 56t7x %}

### Add script logic

The most important step is to add the logic. Make sure you copy the full contents into your `script.js` file.

{% codesandbox 56t7x %}

## Step 5: Understanding the logic of script.js

Let’s try to understand how the above logic works, starting with the bottom of the `script.js` file. This logic replaces the content of the script tag we defined in the `index.html` file. The following snippet creates a simple website with a form that allows the user to create new movie ratings and display all submitted ratings.

```jsx
document.getElementById('movie-app').parentNode.innerHTML = `
  <div class="app">
    <h1 class="main-heading">Welcome to Keystone 5!</h1>
    <p class="intro-text">
      Here's a simple demo app that lets you add/remove movie ratings. Create a few entries, then go
      check them out from your <a href="/admin">Keystone 5 Admin UI</a>!
    </p>
    <hr class="divider" />
    <div class="form-wrapper">
      <h2 class="app-heading">Add Movie</h2>
      <div>
        <form class="js-add-movie-form">
          <input required name="add-item-movie" placeholder="Add new movie" class="form-input add-item" />
          <input required name="add-item-rating" placeholder="Add rating" class="form-input add-item" />
          <input type="submit" value="Submit">
        </form>
      </div>
      <h2 class="app-heading">Movie List</h2>
      <div class="results">
        <p>Loading...</p>
      </div>
    </div>
  </div>`;
```

The rendered interface will look like this:

![Rendered Interface for Keystone.js Movie Rating App](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2020/04/movie-rating-app-rendered-interface-1.png?resize=720%2C727&ssl=1)

Users can submit movies via the form. When you click the submit button, the following code is triggered.

```jsx
function addMovie(event) {
    event.preventDefault();
    const form = event.target;

    // Find inputted data via 'add-item-movie' and 'add-item-rating' input elements
    const movie = form.elements['add-item-movie'];
    const rating = form.elements['add-item-rating'];

    if (movie && rating) {
        graphql(ADD_MOVIE, { title: movie.value, rating: Number(rating.value) }).then(fetchData);
    }

    // Clear the form
    form.reset();
}
```

The code tries to access the data entered in the form’s input fields via the IDs `add-item-movie` and `add-item-rating`. If both the movie title and rating have been input, we’ll call our GraphQL endpoint with the correct data.

Notice that we passed `ADD_MOVIE` as our first parameter. This constant represents a query developed using the GraphQL SDL. The query accepts a title and rating. Since it’s prefixed with the mutation keyword, it can add new data to your database.

```jsx
const ADD_MOVIE = `
    mutation AddMovie($title: String!, $rating: Int!) {
      createMovie(data: { title: $title, rating: $rating }) {
        title
        rating
        id
      }
    }
  `;
```

The `GET_MOVIES` query helps retrieve all movies. As we are reading data, we use the query keyword instead of the mutation keyword. This query displays all movies on the static website.

```jsx
const GET_MOVIES = `
    query GetMovies {
      allMovies {
        title
        rating
        id
      }
    }
  `;
```

Finally, the `REMOVE_MOVIE` constant holds a query for removing movie ratings.

```jsx
const REMOVE_MOVIE = `
    mutation RemoveMovie($id: ID!) {
      deleteMovie(id: $id) {
        title
        rating
        id
      }
    }
  `;
```

But how do we actually access the GraphQL endpoint? The `script.js `file holds a helper function for sending a POST request to our GraphQL endpoint.

```jsx
function graphql(query, variables = {}) {
    return fetch('/admin/api', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
        },
        body: JSON.stringify({
            variables,
            query,
        }),
    }).then(function (result) {
        return result.json();
    }).catch(console.error)
}
```

## Step 6: Final testing

To verify that everything is working correctly, let’s start our application. If the app is still running, exit by hitting CTRL+C (Windows) or CMD+C (Linux/Mac). Next, restart the application and visit the interface at [http://localhost:3000](http://localhost:3000).

```jsx
yarn run dev
```

Try to add a new movie and verify whether they’re added to the movie ratings list below the input form. Next, try to delete a movie by clicking the trash icon on the movie rating. The rating should disappear.

If everything works correctly, you just built your first movie rating application with Kestone.js. Congratulations!

* * *
## 200's only ‎✅: Monitor failed and show GraphQL requests in production 

While GraphQL has some features for debugging requests and responses, making sure GraphQL reliably serves resources to your production app is where things get tougher. If you’re interested in ensuring network requests to the backend or third party services are successful, [try LogRocket.](https://www2.logrocket.com/signup-lr)

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/jd2amkhuiynfjf3i33qk.png)

[LogRocket](https://www2.logrocket.com/signup-lr) is like a DVR for web apps, recording literally everything that happens on your site. Instead of guessing why problems happen, you can aggregate and report on problematic GraphQL requests to quickly understand the root cause. In addition, you can track Apollo client state and inspect GraphQL queries' key-value pairs.

LogRocket instruments your app to record baseline performance timings such as page load time, time to first byte, slow network requests, and also logs Redux, NgRx, and Vuex actions/state. [Start monitoring for free.](https://www2.logrocket.com/signup-lr)
* * *

The post [Create a movie rating app with Keystone.js](https://blog.logrocket.com/create-a-movie-rating-app-with-keystone-js/) appeared first on [LogRocket Blog](https://blog.logrocket.com).