Movie rating web application
Tags: node, vue, sql, chatgpt
Published: 2023-04-06 07:18:18

**Disclaimer: This blog post is created with the help of <u>ChatGPT</u>**

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

```

###Backend setup (Node.js, Express, and SQL):
Install the necessary packages:
```
npm install express sequelize sqlite3 body-parser cors
```

- 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}`);
});
```

### 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;

```

- 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;
```

- 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' });
  }
});


```

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' });
  }
});


```

### Frontend setup (Vue.js):
Install the Vue CLI:

```
npm install -g @vue/cli
```

- Create a new Vue project in a separate folder called frontend:
```
vue create frontend
cd frontend

```


- 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');
```

- 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>
```

#### Register.vue:

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

<script>
export default {
  name: 'Register',
};
</script>
```

- 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>
```

#### 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>
```

- 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>
```

####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>
```

- 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>
```

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"]

```

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;"]
```

- 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
```
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
```
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
```

In the frontend folder, run:

```
docker build -t your-dockerhub-user/movie-rating-frontend:latest .
docker push your-dockerhub-user/movie-rating-frontend:latest
```

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
```
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
```
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.