Institute of Computer Science
  1. Courses
  2. 2023/24 spring
  3. Enterprise System Integration (MTAT.03.229)
ET
Log in

Enterprise System Integration 2023/24 spring

  • Home
  • Lectures
  • Practicals
  • Assignements
  • Project and exam
  • Message Board

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

Main operations our backendProduct service can respond to in terms of their HTTP methods, URIs, and actions
MethodURIAction
GEThttp://localhost:8082/api/productsFetch all products
GEThttp://localhost:8082/api/products/:idFetch a product based on its id
POSThttp://localhost:8082/api/productsCreate a new product
PUThttp://localhost:8082/api/products/:idUpdate a product based on its id
DELETEhttp://localhost:8082/api/products/:idDelete a product based on its id

A simplified representation of the application

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>
  • Institute of Computer Science
  • Faculty of Science and Technology
  • University of Tartu
In case of technical problems or questions write to:

Contact the course organizers with the organizational and course content questions.
The proprietary copyrights of educational materials belong to the University of Tartu. The use of educational materials is permitted for the purposes and under the conditions provided for in the copyright law for the free use of a work. When using educational materials, the user is obligated to give credit to the author of the educational materials.
The use of educational materials for other purposes is allowed only with the prior written consent of the University of Tartu.
Terms of use for the Courses environment