Session 8.3: Vue.js - CRUD
1. Clone the following repository
$ git clone https://github.com/M-Gharib/ESI-W8.2.git
2. Project setup - install dependencies
$ npm install
3. Create the JAR for the backend (server)
$ npm run server-product-package
4. Run the backend (server)
$ npm run server-product
Note: If you have any problems while packaging and running the backend via the command line, you can simply run it manually as we did before.
5. Run the frontend (VueJs project) - in a new terminal
$ npm run serve
Our backend Product service can provide five different request handlers (functions) to deal with the five different HTTP requests we have: (1) fetch all products, (2) fetch a product based on its id, (3) create a product, (4) update a product based on its id, and (5) delete a product based on its id. A simplified representation of the application is shown in Figure 9.3
Method | URI | Action |
GET | http://localhost:8082/api/products | Fetch all products |
GET | http://localhost:8082/api/products/:id | Fetch a product based on its id |
POST | http://localhost:8082/api/products | Create a new product |
PUT | http://localhost:8082/api/products/:id | Update a product based on its id |
DELETE | http://localhost:8082/api/products/:id | Delete a product based on its id |
6. Check the code in server/controller/ProductController.java
, the only new addition is @CrossOrigin(origins = "*")
, which is added to configure allowed origins and avoid the CORS (Cross-Origin Resource Sharing) error. Using "*" means any origin is allowed.
..... @CrossOrigin(origins = "*") @RestController @RequestMapping("/api") public class ProductController { .....
7. Check the code in src/router/index.js
, to see the defined routes. Note how each used component/view within these routes is imported.
import { createRouter, createWebHistory } from 'vue-router' import AllProducts from "../views/AllProducts.vue"; ..... const routes = [{ path: '/', name: 'AllProducts', component: AllProducts, }, ..... { //will route to AllPosts view if none of the previous routes apply path: "/:catchAll(.*)", name: "AllProducts", component: AllProducts, } ] .....
8. Check the code in src/App.vue
, to see the defined router-links.
<template> <nav> <router-link to="/api/allproducts">Products</router-link> | <router-link to="/api/addproduct">Add a Product</router-link> </nav> <router-view/> </template> .....
9. Check the src/views/AllProducts.vue
view, which fetches and presents all products. In short, on mount
, the fetchProducts()
is called, which fetches all products from the back end via a GET request. Then, it assigns the fetched data to the products
array. The products
array is used within the <template> .. </template>
section to present the products using a v-for
directive. Pay attention to how we are putting an anchor <a>..</a>
for each product, when we click on it, we will be directed to the aproduct
view with the product.id
as a route variable.
<template> <div class="AllProducts"> <div id="products-list"> <h1>All Products</h1> <ul> <div class="item" v-for="product in products" :key="product.id"> <!-- We are putting an anchor for each product, when we click on it, we will be directed to the specific product view (/aproduct/) / --> <a class="singleproduct" :href="'/api/aproduct/' + product.id"> <span class="code"> <b>Code:</b> {{ product.code }} </span><br /> <span class="name"> <b>Name:</b> {{ product.name }} </span> <br /> <span class="description"> <b>Description:</b> {{ product.description }} </span> <br /> <span class="price"> <b>Price:</b> {{ product.price }} </span> <br /> </a> </div> </ul> </div> </div> </template> <script> export default { name: "AllProducts", data() { return { products: [], }; }, methods: { fetchProducts() { // fetch is a GET request by default unless stated otherwise. Therefore, it will fetch all products from the database fetch(`http://localhost:8082/api/products`) .then((response) => response.json()) .then((data) => (this.products = data)) .catch((err) => console.log(err.message)); }, }, mounted() { // call fetchProducts() when this element (AllProducts()) mounts this.fetchProducts(); console.log("mounted"); }, }; </script> .....
10. Check the src/views/AddProduct.vue
view, which provides a "form" for entering the attributes of a product, and a button that when pressed on, the product object will be stringified and sent, via a post request, to the back-end to be added to the database. Pay attention to how we are using the v-model
to bind the data in the form inputs to the product variable, and how the addProduct()
function is triggered by the button, how the data object to be sent is created, then, how FetchAPI is used to send the data object via a post request.
<template> <div class="form"> <h3>Add a Product</h3> <label for="id">ID: </label> <input name="id" type="text" id="id" required v-model="product.id" /> <label for="code">Code: </label> <input name="code" type="text" id="code" required v-model="product.code" /> <label for="name">Name: </label> <input name="name" type="text" id="name" required v-model="product.name" /> <label for="description">Description: </label> <input name="description" type="text" id="description" required v-model="product.description"/> <label for="price">Price: </label> <input name="price" type="number" id="price" required v-model="product.price"/> <button @click="addProduct" class="addPost">Add Product</button> </div> </template> <script> export default { name: "AddProduct", data() { return { product: { id: "", code: "", name: "", description: "", price: 0.0, }, }; }, methods: { addProduct() { var data = { id: this.product.id, code: this.product.code, name: this.product.name, description: this.product.description, price: this.product.price, }; // using Fetch - post method - send an HTTP post request to the specified URI with the defined body fetch("http://localhost:8082/api/products", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(data), }) .then((response) => { console.log(response.data); // redirect to /allposts view this.$router.push("/api/allposts"); }) .catch((e) => { console.log(e); console.log("error"); }); }, }, }; </script> .....
11. Check the src/views/AProduct.vue
view, which fetches and presents a single product based on the based id. In short, on mount
, the fetchProduct(this.$route.params.id)
is called, which takes the route parameter id as input, and fetches the specified product from the back end via a GET request. Then, it assigns the fetched product object to the product
object. The product
object is used within the <template> .. </template>
section to present the product.
<template> <div class="A Post"> <div id="form"> <h3>A Product</h3> <label for="code">Code: </label> <input name="code" type="text" id="code" required v-model="product.code" /> <label for="name">Name: </label> <input name="name" type="text" id="name" required v-model="product.name" /> <label for="description">Description: </label> <input name="description" type="text" id="description" required v-model="product.description" /> <label for="price">Price: </label> <input name="price" type="text" id="price" required v-model="product.price" /> </div> <div> <button @click="updateProduct" class="updateProduct">Update Product</button> <button @click="deleteProduct" class="deleteProduct">Delete Product</button> </div> </div> </template> <script> export default { name: "AProduct", data() { return { product: { code: "", name: "", description: "", price: "", }, }; }, methods: { fetchProduct(id) { // fetch one product with the specified id (id) fetch(`http://localhost:8082/api/products/${id}`) .then((response) => response.json()) .then((data) => (this.product = data)) .catch((err) => console.log(err.message)); }, .....
12. Check how the updateProduct()
function in src/views/AProduct.vue
view is triggered by the button, and how it is creating the product object to be sent, then, how it uses FetchAPI to send the post request.
<template> ..... <button @click="deleteProduct" class="deleteProduct">Delete Product</button> ..... </template> <script> ..... methods: { ..... updateProduct() { // using Fetch - put method - updates a specific product based on the passed id and the specified body fetch(`http://localhost:8082/api/products/${this.product.id}`, { method: "PUT", headers: { "Content-Type": "application/json", }, body: JSON.stringify(this.product), }) .then((response) => { console.log(response.data); //this.$router.push("/apost/" + this.product.id); // We are using the router instance of this element to navigate to a different URL location this.$router.push("/api/allproducts"); }) .catch((e) => { console.log(e); }); }, ..... </script>
13. the src/views/AProduct.vue
view contains a button to trigger the deleteProduct()
function. Using what you learned, try to write the code that allows this function to delete a product based on the passed id. Note: the backend has a request handler for a delete request (URI: http://localhost:8082/api/products/). You need to pass the product id for the deleteProduct()
, and you need to specify the "DELETE" method for the FetchAPI request, like what we did for the updateProduct()
, where we have specified the method as "PUT".
<template> ..... <button @click="deleteProduct" class="deleteProduct">Delete Product</button> ..... </template> <script> ..... methods: { ..... deleteProduct() { }, ..... </script>