Movie rating web application

habeebahmed

habeebahmed

Posted on April 6, 2023

Movie rating web application

Disclaimer: This blog post is created with the help of ChatGPT

We will be developing the code from scratch.

The features we shall be covering are:

  1. Rate Movie.
  2. Authentication.
  3. Deployment using Kubernetes.

Project setup:

Create a new directory for your project and initialize it with npm:

mkdir movie-rating-app
cd movie-rating-app
npm init -y

Enter fullscreen mode Exit fullscreen mode

Backend setup (Node.js, Express, and SQL):

Install the necessary packages:

npm install express sequelize sqlite3 body-parser cors
Enter fullscreen mode Exit fullscreen mode
  • Create a server.js file in your project's root directory:
const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors');
const { Sequelize } = require('sequelize');

const app = express();
const port = 3000;

app.use(cors());
app.use(bodyParser.json());

// Initialize Sequelize
const sequelize = new Sequelize({
  dialect: 'sqlite',
  storage: './database.sqlite',
});

(async () => {
  try {
    await sequelize.authenticate();
    console.log('Connection to the database has been established successfully.');
  } catch (error) {
    console.error('Unable to connect to the database:', error);
  }
})();

const movieRoutes = require('./routes/movies');
const userRoutes = require('./routes/users');

app.use('/movies', movieRoutes);
app.use('/users', userRoutes);

app.listen(port, () => {
  console.log(`Server is running on port ${port}`);
});
Enter fullscreen mode Exit fullscreen mode

Create models and routes for movies and users:

Create a models directory and add two files movie.js and user.js for the movie and user models, respectively.

movie.js:

const { DataTypes } = require('sequelize');
const sequelize = require('../server');

const Movie = sequelize.define('Movie', {
  title: {
    type: DataTypes.STRING,
    allowNull: false,
  },
  rating: {
    type: DataTypes.FLOAT,
    allowNull: false,
  },
});

module.exports = Movie;

Enter fullscreen mode Exit fullscreen mode
  • user.js:
const { DataTypes } = require('sequelize');
const sequelize = require('../server');

const User = sequelize.define('User', {
  username: {
    type: DataTypes.STRING,
    allowNull: false,
    unique: true,
  },
  password: {
    type: DataTypes.STRING,
    allowNull: false,
  },
});

module.exports = User;
Enter fullscreen mode Exit fullscreen mode
  • Create a routes directory and add two files movies.js and users.js for the movie and user routes, respectively.

movies.js:

// Get all movies
router.get('/', async (req, res) => {
  try {
    const movies = await Movie.findAll();
    res.json(movies);
  } catch (error) {
    res.status(500).json({ message: 'Error fetching movies' });
  }
});

// Add a new movie
router.post('/', async (req, res) => {
  try {
    const movie = await Movie.create({
      title: req.body.title,
      rating: req.body.rating,
    });
    res.status(201).json(movie);
  } catch (error) {
    res.status(500).json({ message: 'Error adding movie' });
  }
});

// Update a movie's rating
router.put('/:id', async (req, res) => {
  try {
    const movie = await Movie.findByPk(req.params.id);
    if (!movie) {
      return res.status(404).json({ message: 'Movie not found' });
    }
    movie.rating = req.body.rating;
    await movie.save();
    res.json(movie);
  } catch (error) {
    res.status(500).json({ message: 'Error updating movie rating' });
  }
});


Enter fullscreen mode Exit fullscreen mode

users.js:

const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');

// Register a new user
router.post('/register', async (req, res) => {
  try {
    const hashedPassword = await bcrypt.hash(req.body.password, 10);
    const user = await User.create({
      username: req.body.username,
      password: hashedPassword,
    });
    res.status(201).json(user);
  } catch (error) {
    res.status(500).json({ message: 'Error registering user' });
  }
});

// Authenticate a user and return a JWT
router.post('/login', async (req, res) => {
  try {
    const user = await User.findOne({ where: { username: req.body.username } });
    if (!user) {
      return res.status(400).json({ message: 'User not found' });
    }

    const match = await bcrypt.compare(req.body.password, user.password);
    if (!match) {
      return res.status(401).json({ message: 'Invalid credentials' });
    }

    const token = jwt.sign({ id: user.id }, 'secret', { expiresIn: '1h' });
    res.json({ token });
  } catch (error) {
    res.status(500).json({ message: 'Error logging in' });
  }
});


Enter fullscreen mode Exit fullscreen mode

Frontend setup (Vue.js):

Install the Vue CLI:

npm install -g @vue/cli
Enter fullscreen mode Exit fullscreen mode
  • Create a new Vue project in a separate folder called frontend:
vue create frontend
cd frontend

Enter fullscreen mode Exit fullscreen mode
  • Install required packages:
  • In the frontend folder, run:

npm install axios vue-router

  • Set up routing in src/main.js:
import { createApp } from 'vue';
import { createRouter, createWebHistory } from 'vue-router';
import App from './App.vue';

// Import views
import Home from './views/Home.vue';
import Login from './views/Login.vue';
import Register from './views/Register.vue';

// Set up router
const router = createRouter({
  history: createWebHistory(),
  routes: [
    { path: '/', component: Home },
    { path: '/login', component: Login },
    { path: '/register', component: Register },
  ],
});

const app = createApp(App);
app.use(router);
app.mount('#app');
Enter fullscreen mode Exit fullscreen mode
  • Create views for Home, Login, and Register:
  • In src/views, create Home.vue, Login.vue, and Register.vue.

Home.vue:

<template>
  <div>
    <h1>Movies</h1>
    <!-- Add components to display movies and their ratings here -->
  </div>
</template>

<script>
export default {
  name: 'Home',
};
</script>
Login.vue:

html
Copy code
<template>
  <div>
    <h1>Login</h1>
    <!-- Add components to handle user login here -->
  </div>
</template>

<script>
export default {
  name: 'Login',
};
</script>
Enter fullscreen mode Exit fullscreen mode

Register.vue:

<template>
  <div>
    <h1>Register</h1>
    <!-- Add components to handle user registration here -->
  </div>
</template>

<script>
export default {
  name: 'Register',
};
</script>
Enter fullscreen mode Exit fullscreen mode
  • Implement components for handling user registration and login:
  • In src/components, create LoginForm.vue and RegisterForm.vue.

LoginForm.vue:

<template>
  <form @submit.prevent="submit">
    <label for="username">Username</label>
    <input type="text" id="username" v-model="username" required />

    <label for="password">Password</label>
    <input type="password" id="password" v-model="password" required />

    <button type="submit">Login</button>
  </form>
</template>

<script>
import axios from 'axios';

export default {
  data() {
    return {
      username: '',
      password: '',
    };
  },
  methods: {
    async submit() {
      try {
        const response = await axios.post('http://localhost:3000/users/login', {
          username: this.username,
          password: this.password,
        });
        localStorage.setItem('token', response.data.token);
        this.$router.push('/');
      } catch (error) {
        console.error('Login failed', error);
      }
    },
  },
};
</script>
Enter fullscreen mode Exit fullscreen mode

RegisterForm.vue:

<template>
  <form @submit.prevent="submit">
    <label for="username">Username</label>
    <input type="text" id="username" v-model="username" required />

    <label for="password">Password</label>
    <input type="password" id="password" v-model="password" required />

    <button type="submit">Register</button>
  </form>
</template>

<script>
import axios from 'axios';

export default {
  data() {
    return {
      username: '',
      password: '',
    };
  },
  methods: {
    async submit() {
      try {
        await axios.post('http://localhost:3000/users/register', {
          username: this.username,
          password: this.password,
        });
        this.$router.push('/login');
      } catch (error) {
        console.error('Registration failed', error);
      }
    },
  },
};
</script>
Enter fullscreen mode Exit fullscreen mode
  • Include the LoginForm and RegisterForm components in Login.vue and Register.vue, respectively.

  • Implement components for displaying and rating movies:
    In src/components, create MovieList.vue and MovieRating.vue for displaying the list of movies and handling movie ratings, respectively.

MovieList.vue:

<template>
  <div>
    <ul>
      <li v-for="movie in movies" :key="movie.id">
        {{ movie.title }} - {{ movie.rating }}
        <MovieRating :movie="movie" @update="updateRating" />
      </li>
    </ul>
  </div>
</template>

<script>
import axios from 'axios';
import MovieRating from './MovieRating.vue';

export default {
  components: {
    MovieRating,
  },
  data() {
    return {
      movies: [],
    };
  },
  async created() {
    try {
      const response = await axios.get('http://localhost:3000/movies');
      this.movies = response.data;
    } catch (error) {
      console.error('Error fetching movies', error);
    }
  },
  methods: {
    updateRating(movie) {
      const index = this.movies.findIndex((m) => m.id === movie.id);
      this.movies[index] = movie;
    },
  },
};
</script>
Enter fullscreen mode Exit fullscreen mode

MovieRating.vue:

<template>
  <div>
    <button @click="changeRating(-1)">-</button>
    <button @click="changeRating(1)">+</button>
  </div>
</template>

<script>
import axios from 'axios';

export default {
  props: {
    movie: Object,
  },
  methods: {
    async changeRating(delta) {
      const newRating = this.movie.rating + delta;
      if (newRating < 0 || newRating > 10) {
        return;
      }

      try {
        const response = await axios.put(`http://localhost:3000/movies/${this.movie.id}`, {
          rating: newRating,
        });
        this.$emit('update', response.data);
      } catch (error) {
        console.error('Error updating movie rating', error);
      }
    },
  },
};
</script>
Enter fullscreen mode Exit fullscreen mode
  • Include MovieList and MovieRating components in Home.vue:
  • Update Home.vue to include the MovieList component:
<template>
  <div>
    <h1>Movies</h1>
    <MovieList />
  </div>
</template>

<script>
import MovieList from '../components/MovieList.vue';

export default {
  name: 'Home',
  components: {
    MovieList,
  },
};
</script>
Enter fullscreen mode Exit fullscreen mode

Now that we have our frontend and backend applications complete, we can focus on containerizing the applications and deploying them using Kubernetes.

Containerize the applications:
To containerize the applications, we'll need to create Dockerfiles for both the frontend and backend applications.

a. Create a Dockerfile in the backend folder with the following contents:

FROM node:14

WORKDIR /app

COPY package*.json ./

RUN npm install

COPY . .

EXPOSE 3000

CMD ["npm", "start"]

Enter fullscreen mode Exit fullscreen mode

b. Create a Dockerfile in the frontend folder with the following contents:

FROM node:14 AS build

WORKDIR /app

COPY package*.json ./

RUN npm install

COPY . .

RUN npm run build

FROM nginx:1.19

COPY --from=build /app/dist /usr/share/nginx/html

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]
Enter fullscreen mode Exit fullscreen mode
  • Create a Kubernetes cluster:
    We can use managed Kubernetes services like Google Kubernetes Engine (GKE), Amazon Elastic Kubernetes Service (EKS), or Azure Kubernetes Service (AKS) to create a Kubernetes cluster. Alternatively, We can use minikube for local testing. The instructions for setting up a Kubernetes cluster are beyond the scope of this response, but we can follow the official Kubernetes documentation for guidance: https://kubernetes.io/docs/setup/

  • Create Kubernetes manifests:
    We'll need to create Kubernetes manifests to deploy our applications. In a new folder named k8s, create the following files:

a. backend-deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: movie-rating-backend
spec:
  replicas: 1
  selector:
    matchLabels:
      app: movie-rating-backend
  template:
    metadata:
      labels:
        app: movie-rating-backend
    spec:
      containers:
      - name: backend
        image: your-dockerhub-user/movie-rating-backend:latest
        ports:
        - containerPort: 3000
---
apiVersion: v1
kind: Service
metadata:
  name: movie-rating-backend
spec:
  selector:
    app: movie-rating-backend
  ports:
  - protocol: TCP
    port: 3000
    targetPort: 3000
  type: LoadBalancer
Enter fullscreen mode Exit fullscreen mode

b. frontend-deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: movie-rating-frontend
spec:
  replicas: 1
  selector:
    matchLabels:
      app: movie-rating-frontend
  template:
    metadata:
      labels:
        app: movie-rating-frontend
    spec:
      containers:
      - name: frontend
        image: your-dockerhub-user/movie-rating-frontend:latest
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: movie-rating-frontend
spec:
  selector:
    app: movie-rating-frontend
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
  type: LoadBalancer
Enter fullscreen mode Exit fullscreen mode

Replace your-dockerhub-user with your Docker Hub username.

Deploy the applications:
To deploy the applications, follow these steps:

a. Build and push the Docker images:

In the backend folder, run:

docker build -t your-dockerhub-user/movie-rating-backend:latest .
docker push your-dockerhub-user/movie-rating-backend:latest
Enter fullscreen mode Exit fullscreen mode

In the frontend folder, run:

docker build -t your-dockerhub-user/movie-rating-frontend:latest .
docker push your-dockerhub-user/movie-rating-frontend:latest
Enter fullscreen mode Exit fullscreen mode

Replace your-dockerhub-user with your Docker Hub username.

b. Deploy the backend and frontend applications to our Kubernetes cluster:

Make sure we have kubectl installed and configured to connect to our Kubernetes cluster. Then, in the k8s folder, run:

kubectl apply -f backend-deployment.yaml
kubectl apply -f frontend-deployment.yaml
Enter fullscreen mode Exit fullscreen mode

This will deploy the backend and frontend applications to our Kubernetes cluster and create LoadBalancer services for both applications. We can find the external IP addresses for these services using the following command:

kubectl get services
Enter fullscreen mode Exit fullscreen mode

Once we have the external IP addresses, we can access our movie-rating application via a web browser using the frontend service's IP address. Make sure to update the API URLs in the frontend application (axios calls) to point to the external IP address of the backend service.

💖 💪 🙅 🚩
habeebahmed
habeebahmed

Posted on April 6, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Movie rating web application
node Movie rating web application

April 6, 2023