06. Integration
Now we will integrate frontend with the backend. But before this I will create a folder in the src of frontend with name utils there I will keep creating utility functions for our frontend.
In this utility folder we create helpers for our application.
Let's start with Registration in our application.
Register
Create register component in the frontend app
js filename=src/views/RegisterPage.vue
<template>
<FormCompo>
<template v-slot:form>
<h5 class="card-title">Sign up</h5>
<form @submit.prevent="submitForm">
<div class="mb-3">
<label class="form-label">Email address</label>
<input type="email" v-model="email" class="form-control" required>
<div v-if="message" class="alert alert-warning">
{{ message }}
</div>
</div>
<div class="mb-3">
<label class="form-label">Your Name</label>
<input type="text" v-model="name" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label">Password</label>
<input type="password" v-model="password" class="form-control">
</div>
<div class="mb-3">
<select class=" input is-large" v-model="role" required>
<option class="input is-large" value="user">User</option>
<option class="input is-large" value="manager">Manager</option>
</select>
</div>
<button type="submit" class="btn btn-outline-primary">Sign up</button>
</form>
</template>
</FormCompo>
</template>
<script>
import FormCompo from '../components/FormCompo.vue';
import home from '../utils/navigation.js';
export default {
name: 'LoginPage',
components: {
FormCompo
},
data() {
return {
email: '',
name: '',
password: '',
role: '',
message: ''
}
},
methods: {
home() {
home(this.$store, this.$route, this.$router);
},
async submitForm() {
try {
const response = await fetch('http://127.0.0.1:5000/signup', {
method: 'POST',
mode: 'cors',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"email": this.email,
"name": this.name,
"password": this.password,
"role": this.role
}),
});
if (response.ok) {
const data = await response.json();
alert("User created successfully");
if (this.$route.path != '/login') {
this.$router.push('/login')
this.home()
}
} else {
const data = await response.json();
throw new Error(data.error);
}
} catch (error) {
alert(error);
}
}
}
}
</script>
Here we are using a parent FormCompo
which we will define next. This FormCompo
will be the basic UI of a general form which we will use to submit data from client to server.
Note
<template v-slot:form>
is the placeholder which we have created in the parent FormCompo.vue
template for dynamic templating.
The submitForm()
is the api call method which will be called when the form is submitted.
Now let's create the FormCompo.vue file.
vue filename=src/components/FormCompo.vue
<template>
<div class="row justify-content-center m-3">
<div class="card bg-light" style="width: 18rem;">
<div class="card-body">
<div class="d-flex justify-content-end">
<!-- Cross button to close the card -->
<button
type="button"
class="btn-close"
aria-label="Close"
@click="home"
></button>
</div>
<slot name="form"></slot>
</div>
</div>
</div>
</template>
<script type="module">
import home from "../utils/navigation";
export default {
name: "FormCompo",
methods: {
home() {
home(this.$store, this.$route, this.$router);
},
},
};
</script>
You might have notice the home
method in the FormCompo
component, this is the method which will be called when the cross button is clicked.
Let's also create the helper function.
js filename=src/utils/navigation.js
function home(store, route, router) {
const role = store.state.authenticatedUser.role;
const targetRoute =
role === "admin"
? "/admin"
: role === "manager"
? "/manager"
: role === "user"
? "/user"
: "/";
if (route.path !== targetRoute) {
router.push(targetRoute);
}
}
export default home;
Login
vue filename=src/views/LoginPage.vue
<template>
<FormCompo>
<template v-slot:form>
<h5 class="card-title">Sign In</h5>
<form @submit.prevent="submitForm">
<div class="mb-3">
<label for="exampleInputEmail1" class="form-label"
>Email address</label
>
<input
type="email"
v-model="email"
class="form-control"
id="exampleInputEmail1"
/>
<div v-if="message" class="alert alert-warning">
{{ message }}
</div>
</div>
<div class="mb-3">
<label for="exampleInputPassword1" class="form-label"
>Password</label
>
<input
type="password"
v-model="password"
class="form-control"
id="exampleInputPassword1"
/>
</div>
<button type="submit" class="btn btn-outline-primary">
Login
</button>
</form>
</template>
</FormCompo>
</template>
<script>
import FormCompo from "../components/FormCompo.vue";
export default {
name: "LoginPage",
components: {
FormCompo,
},
data() {
return {
email: "",
name: "",
password: "",
role: "",
remember: "",
message: "",
};
},
methods: {
closeCard() {
this.$router.push("/");
},
async submitForm() {
try {
// create operation
const response = await fetch(
"http://127.0.0.1:5000/api/login",
{
method: "POST",
mode: "cors",
credentials: "include",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
email: this.email,
password: this.password,
}),
}
);
if (!response.ok) {
const data = await response.json();
console.log(data);
throw new Error(data.error);
} else {
const data = await response.json();
localStorage.setItem("jwt", data.access_token);
localStorage.setItem("role", data.role);
console.log(data);
this.$store.commit("setAuthenticatedUser", data);
if (data.role === "admin") {
this.$router.push("/admin");
} else if (data.role === "manager") {
this.$router.push("/manager");
} else if (data.role === "user") {
this.$router.push("/user");
}
}
} catch (error) {
alert(error);
}
},
},
};
</script>
We will store the access token in localstorage.
Note: v-model is two way binding, which means that the value of the input field will be updated in the data object, and the data object will be updated in the input field.
js filename=static/store/index.js
const store = createStore({
state: {
products: [],
categories: [],
authenticatedUser: "" // [tl! add]
},
getters: {
// write your code here
},
mutations: {
...
setAuthenticatedUser: (state, user) => {
state.authenticatedUser = user;
},
},
actions: {
// write your code here
},
});
export default store;
We have created a variable to store the user data in the store.
Now we will to create CRUD api calls for category and product.
Create Category
js filename=static/components/CreateCatCompo.js
<template>
...
</template>
<script type="module">
import FormCompo from '../components/FormCompo.vue'; // [tl! add]
import { addCategory } from '../services/apiServices'; // [tl! add]
import home from '../utils/navigation'; // [tl! add]
export default {
name: 'CreateCatCompo',
components: {
FormCompo //component binding
},
data() {
return {
name: '',
message: ''
}
},
methods: {
home() {
home(this.$store, this.$route, this.$router);
},
async addcategory() {
/* write your code here */ // [tl! add:start]
try {
const response = await addCategory(this.name);
this.$store.commit('addCat', response.data.resource);
this.home();
} catch (error) {
console.error(error);
} // [tl! add:end]
}
}
}
</script>
Here you can see that we are importing the addCategory
function from the apiServices
file and using it to add a new category.
Create Product
js filename=static/components/CreateProCompo.js
<template>
...
</template>
<script type="module">
import FormCompo from './FormCompo.vue'; // [tl! add]
import { addProduct } from '../services/apiServices'; // [tl! add]
export default {
name: 'CreateProCompo',
components: {
FormCompo
},
data() {
return {
...
}
},
methods: {
// write your code here
...
async addProduct() { // [tl! add:start]
try {
const response = await addProduct(this.name, this.quantity, this.manufacture, this.expiry, this.rpu, this.unit,
this.description, this.image, this.category_id);
if (this.$store.state.authenticatedUser.role === 'admin') {
this.$store.commit('addProduct', data.resource)
}
else {
this.$store.commit('addNoti', data.resource)
}
} catch (error) {
console.error(error);
}
}// [tl! add:end]
}
}
</script>
Update Category
js filename=static/components/EditCatCompo.js
<template>
...
</template>
<script>
export default {
import FormCompo from './FormCompo.vue'; //[tl! add:start]
import home from '../utils/navigation';
import { updateCategory, fetchCategory, deleteCategory } from '../services/apiServices'; //[tl! add:end]
name: 'EditCatCompo',
components: {
FormCompo
},
data() {
return {
name: '',
message: ''
}
},
methods: {
// write your code here
async fetchcategory() { //[tl! add:start]
try {
const data = await fetchCategory(this.$route.params.id);
console.log(data);
this.name = data.name;
} catch (error) {
console.error(error);
}
},
async updatecategory() {
if (confirm("Are you sure?")) {
try {
const data = await updateCategory(this.$route.params.id, this.name);
if (this.$store.state.authenticatedUser.role === 'admin') {
this.$store.commit('updateCategory', data.resource);
}
else {
this.$store.commit('addNoti', data.resource)
}
this.home();
} catch (error) {
console.error(error);
}
}
},
async deletecategory() {
if (confirm("Are you sure?")) {
try {
const data = await deleteCategory(this.$route.params.id);
if (this.$store.state.authenticatedUser.role === 'admin') {
this.$store.commit('deleteCategory', data.resource.id);
}
else {
this.$store.commit('addNoti', data.resource)
}
this.home();
} catch (error) {
console.error(error);
}
}
}//[tl! add:start]
},
mounted() {
this.fetchcategory()
}
}
</script>
Update Category
js filename=static/components/EditProCompo.js
<template>
...
</template>
<script type="module">
import FormCompo from './FormCompo.vue'; // [tl! add]
import { updateProduct, fetchProduct, deleteProduct } from '../services/apiServices'; // [tl! add]
export default {
name: 'EditProCompo',
components: {
FormCompo
},
data() {
return {
...
}
},
methods: {
handleFileUpload(event) {
this.product.image = event.target.files[0];
},
async fetchproduct() { // [tl! add: start]
try {
const data = await fetchProduct(this.$route.params.id);
console.log(data);
this.product = data;
} catch (error) {
console.error(error);
}
},
async updateproduct() {
if (confirm("Are you sure?")) {
try {
const data = await updateProduct(this.$route.params.id, this.name, this.quantity, this.manufacture, this.expiry, this.rpu, this.unit, this.description, this.image, this.category_id);
if (this.$store.state.authenticatedUser.role === 'admin') {
this.$store.commit('updateProduct', data.resource);
}
else {
this.$store.commit('addNoti', data.resource)
}
} catch (error) {
console.error(error);
}
}
},
async deleteproduct() {
if (confirm("Are you sure?")) {
try {
const data = await deleteProduct(this.$route.params.id);
if (this.$store.state.authenticatedUser.role === 'admin') {
this.$store.commit('deleteProduct', data.resource.id);
}
else {
this.$store.commit('addNoti', data.resource)
}
} catch (error) {
console.error(error);
}
}
}// [tl! add: end]
},
mounted() {
// write your code here
}
}
</script>
Now we will create a file name apiServices
and add the following code:
js filename=static/services/apiServices.js
const API_BASE = 'http://127.0.0.1:5000';
function getHeader() {
return {
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem('jwt')}`
};
}
async function addCategory(name) {
return await fetch(`${API_BASE}/add/cat`, {
method: 'POST',
credentials: 'include',
headers: getHeader(),
body: JSON.stringify({ name })
}).then(handleResponse);
}
async function addProduct(name, quantity, manufacture, expiry, rpu, unit, description, image, category_id) {
const formData = new FormData();
formData.append('name', name);
formData.append('quantity', quantity);
formData.append('manufacture', manufacture);
formData.append('expiry', expiry);
formData.append('rpu', rpu);
formData.append('unit', unit);
formData.append('description', description);
formData.append('image', image);
formData.append('category_id', category_id);
return await fetch(`${API_BASE}/add/product`, {
method: 'POST',
credentials: 'include',
headers: getHeader(),
body: formData
}).then(handleResponse);
}
async function fetchCategory(id) {
return await fetch(`${API_BASE}/update/category/${id}`, {
method: 'GET',
credentials: 'include',
headers: getHeader()
}).then(handleResponse);
}
async function updateCategory(id, name) {
return await fetch(`${API_BASE}/update/category/${id}`, {
method: 'PUT',
credentials: 'include',
headers: getHeader(),
body: JSON.stringify({ name })
}).then(handleResponse);
}
async function fetchProduct(id) {
return await fetch(`${API_BASE}/update/product/${id}`, {
method: 'GET',
credentials: 'include',
headers: getHeader()
}).then(handleResponse);
}
async function updateProduct(id, name, quantity, manufacture, expiry, rpu, unit, description, image, category_id) {
const formData = new FormData();
formData.append('name', name);
formData.append('quantity', quantity);
formData.append('manufacture', manufacture);
formData.append('expiry', expiry);
formData.append('rpu', rpu);
formData.append('unit', unit);
formData.append('description', description);
formData.append('image', image);
formData.append('category_id', category_id);
return await fetch(`${API_BASE}/update/product/${id}`, {
method: 'PUT',
credentials: 'include',
headers: getHeader(),
body: formData
}).then(handleResponse);
}
async function deleteCategory(id) {
return await fetch(`${API_BASE}/update/category/${id}`, {
method: 'DELETE',
credentials: 'include',
headers: getHeader()
}).then(handleResponse);
}
export {
addCategory,
addProduct,
fetchCategory,
updateCategory,
deleteCategory,
fetchProduct,
updateProduct,
deleteProduct,
};
Now we have successfully created the all api services in this apiServices.js
file.
CORS Configuration
Since we have created two separate applications. Frontend and Backend both are running on different origins with different ports. By default browser does not allow to make a cross site request. It is like a application cannot make request to different origins. This is a security measure. So we need to configure the browser to allow cross site request.
Note You can read more about CORS here
developer.mozilla.org/en-US/docs/Web/HTTP/CORS
.
Backend configuration for cross site request.
python filename=main.py
...
def create_app():
app = Flask(__name__)
CORS(app, origins=['http://localhost:5173'], supports_credentials=True)
return app
if __name__ == "__main__":
app.run()
supports_credentials=True
is used to allow the browser to send cookies in the request. This is required if you want to use authentication with JWT or session cookies.
Testing it out
Now you can start the backend and frontend and test all the features whatever we have implemented so far.
To start the backend run the following command:
python3 main.py
To start the frontend run the following command:
npm run dev
Let's commit the change using the following command.
git add .
git commit -m "database integrated"