habeebahmed
Posted on April 6, 2023
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:
- Rate Movie.
- Authentication.
- 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.
Posted on April 6, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.