4

Vue(3.0) Composition API | Bootstrap(v5) | JSON Server | CRUD Example

 1 year ago
source link: https://www.learmoreseekmore.com/2023/03/vue3-composition-api-bootstrap5-jsonserver-crud-example.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Part-1%20NestJS(v9)%20%20Angular(v14)%20%20MongoDB%20%20CRUD%20Example.png

In this article, we are going to implement a sample Vue(3.0) CRUD example, using JSON Server(Fake API).

Vue(3.0):

Vue(3.0) is a javascript framework for creating a single-page application. Vue application built by component. The components are the smallest unit of the application which comprises 'Script', 'Template(HTML)', and 'Style'. Eventually, multiple components together create the Vue application.
1.jpg

Create Vue(3.0) Application:

Let's create a sample Vue(3.0) application to accomplish our demo.
To create a VueJS application our local machine should contain NodeJS. So go to "https://nodejs.org/en/download" and download the Node.
2.JPG
Now run the below command to create the Vue3 application.
 npm init vue@latest
On running the above command we have to choose a few options before creating a vue application, in those options we can choose the default option as 'No', but for the routing option select 'Yes' like below.
1.PNG
Now open our vue application in 'Visual Studio Code' editor and then run the 'npm install' command to download the required packages.
Let's explore the project default files & folders structure:
package.json -  the 'package.json' file contains package references, commands, etc.
index.html -  the 'index.html' file is the only HTML file of our vue application. It contains a 'div' element with  'id' value 'app', so inside of those elements our components get rendered.
main.js - entry js file.
App.vue - entry vue component.
views(folder) - contains vue component that acts as an individual page.
router(folder) - contains js file which contains route configuration.
components(folder) - contains vue components that can be used as children's components
assets(folder) - contains static files like images.
Run the below command to start the VueJS application under the local server.
 npm run dev

Setup JSON Server(Fake API):

Let's set up a fake API by setting up the JSON server in our local machine.
Run the below command to install the JSON server globally onto your local system.
npm install -g json-server
Now go to our Vue application and add a command to run the JSON server into the 'pakage.json' file
2.PNG

Now to invoke the above added command, run the below command in vue application root folder

npm run json-run
After running the above command for the first time, a 'db.json' file gets created, so this file act as a database. So let's add some sample data to the file as below.
3.PNG
Now access the fake JSON endpoint like 'http://localhost:3000/wonders'
4.PNG

Configure Bootstrap Menu:

In the 'index.html' add the bootstrap CSS and JS file references.
index.html:


  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8" />
  5. <link rel="icon" href="/favicon.ico" />
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  7. <title>Vite App</title>
  8. <link
  9. href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"
  10. rel="stylesheet"
  11. integrity="sha384-GLhlTQ8iRABdZLl6O3oVMWSktQOp6b7In1Zl3/Jr59b6EGGoI1aFkw7cmDA6j6gD"
  12. crossorigin="anonymous"
  13. />
  14. </head>
  15. <body>
  16. <div id="app"></div>
  17. <script type="module" src="/src/main.js"></script>
  18. <script
  19. src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"
  20. integrity="sha384-w76AqPfDkMBDXo30jS1Sgez6pr3x5MlQ1ZAGC+nuZB+EYdgRZgiwxhTBTkF7CXvN"
  21. crossorigin="anonymous"
  22. ></script>
  23. </body>
  24. </html>
  • (Line: 8-13) Bootstrap CSS reference
  • (Line: 18-22) Bootstrap JS reference

Next, remove the default CSS file 'src/assets/main.css' and also remove its reference from the 'main.js'

Now let's update the 'App.vue' component with the bootstrap menu as follows.
src/App.vue:


  1. <script setup>
  2. import { RouterLink, RouterView } from "vue-router";
  3. </script>
  4. <template>
  5. <nav class="navbar bg-primary" data-bs-theme="dark">
  6. <div class="container-fluid">
  7. <a class="navbar-brand" href="#">7 Wonders Of The World</a>
  8. </div>
  9. </nav>
  10. <div class="container">
  11. <RouterView />
  12. </div>
  13. </template>
  14. <style scoped>
  15. </style>
  • Here added our bootstrap. The 'RouterView' is the default component loads from the 'vue-router'. The 'RouterView' component renders the page content
5.PNG

Install Axio Library:

To consume API calls we need to install Axios library
npm install axios

Create A 'AllWonders' Vue Component:

Let's create a new vue component 'AllWonders.vue' in 'src/views/wonders'(new folder).
6.PNG
Now configure the route for 'AllWonders.vue' component in the 'router/index.js'.
src/router/index.js:


  1. import { createRouter, createWebHistory } from "vue-router";
  2. import AllWonders from "../views/wonders/AllWonders.vue";
  3. const router = createRouter({
  4. history: createWebHistory(import.meta.env.BASE_URL),
  5. routes: [
  6. path: "/",
  7. name: "home",
  8. component: AllWonders,
  9. export default router;
  • Here configured a route like '/' to our 'AllWonders' component.

Implement Read Operation:

Read operation means invoking the HTTP GET endpoint, and rendering the API response to the UI.
src/views/wonders/AllWonders.vue:


  1. <script setup>
  2. import { ref, onMounted } from "vue";
  3. import axios from "axios";
  4. const allWonders = ref([]);
  5. onMounted(() => {
  6. axios.get("http://localhost:3000/wonders").then((response) => {
  7. allWonders.value = response.data;
  8. </script>
  9. <template>
  10. <div class="container mt-3">
  11. <div class="row row-cols-1 row-cols-md-3 g-4">
  12. <div class="col" v-for="item in allWonders" :key="item.id">
  13. <div class="card">
  14. <img :src="item.imageUrl" class="card-img-top" alt="..." />
  15. <div class="card-body">
  16. <h5 class="card-title">{{ item.name }}</h5>
  17. <p class="card-text">Location: {{ item.location }}</p>
  18. </div>
  19. </div>
  20. </div>
  21. </div>
  22. </div>
  23. </template>
  • (Line: 1) The 'setup' attribute added to the script tag that represents we are using the composition API approach.
  • (Line: 4) Declared 'allWonders' variable of type 'ref'. The 'ref' type is used to declare the reactive properties. Here 'ref([])' means an empty array will be the initial value for the 'allWonders' variable. The 'ref' imports from the 'vue' library.
  • (Line: 5) The  'onMounted' is the life cycle method. Here we can add our logic that needs to be executed on the component mounted. Here generally we invoke our APIS whose response we want to display on page load.
  • (Line: 6-8) Using 'axios.get()' invoking our HTTP GET API endpoint. Here API response assigned to  'allWonders' variable. In vue to assign value to 'ref' variable we should assign to '{variable}.value', we can't directly assign it to the variable itself.
  • (Line: 12-24) Inside the 'template' element we have to render all our component HTML content.
  • (Line: 14) The 'v-for' attribute is used to loop the HTML element based on the collection variable(allWonders). The ':key' attribute is assigned with a unique values like 'id' this helps with HTML track.
  • In vue to render any data dynamically or data binding we have to use ''{{}}"

Now run both vue application and Json Server(fake API).

7.PNG

Create A 'AddWonders.vue' Component:

Let's create a new Vue component 'AddWornders.vue' in 'src/views/wonders'.
11.PNG
Now configure routing for 'AddWonders.vue' component in the 'router/index.js'.
src/router/index.js:


  1. import { createRouter, createWebHistory } from "vue-router";
  2. import AllWonders from "../views/wonders/AllWonders.vue";
  3. import AddWonders from "../views/wonders/AddWonders.vue";
  4. const router = createRouter({
  5. history: createWebHistory(import.meta.env.BASE_URL),
  6. routes: [
  7. path: "/",
  8. name: "home",
  9. component: AllWonders,
  10. path: "/add-wonder",
  11. name: "Add Wonder",
  12. component: AddWonders,
  13. export default router;
  • (Line: 12-16) Configured the '/add-wonder' router for 'AddWonders.vue' component.

Implement Create Operation:

The Create operation means posting the data to the HTTP POST API call for creating the new item.
src/views/wonders/AddWonders.vue:


  1. <script setup>
  2. import axios from "axios";
  3. import { reactive } from "vue";
  4. import { useRouter } from "vue-router";
  5. let newWonder = reactive({
  6. name: "",
  7. location: "",
  8. imageUrl: "",
  9. const router = useRouter();
  10. const addNewWonder = () => {
  11. axios.post("http://localhost:3000/wonders", newWonder).then(() => {
  12. router.push("/");
  13. </script>
  14. <template>
  15. <div class="container mt-4 w-50">
  16. <form @submit.prevent="addNewWonder">
  17. <legend >Add New Wonder's</legend>
  18. <div class="mb-3">
  19. <label for="txtwonderName" class="form-label">Wonder Name</label>
  20. <input
  21. type="text"
  22. v-model="newWonder.name"
  23. class="form-control"
  24. id="txtwonderName"
  25. />
  26. </div>
  27. <div class="mb-3">
  28. <label for="txtLocation" class="form-label">Location</label>
  29. <input
  30. type="text"
  31. v-model="newWonder.location"
  32. class="form-control"
  33. id="txtLocation"
  34. />
  35. </div>
  36. <div class="mb-3">
  37. <label for="txtImageUrl" class="form-label">Image URL</label>
  38. <input
  39. type="text"
  40. v-model="newWonder.imageUrl"
  41. class="form-control"
  42. id="txtImageUrl"
  43. />
  44. </div>
  45. <button type="submit" class="btn btn-primary">Add</button>
  46. </form>
  47. </div>
  48. </template>
  • (Line: 1) The 'setup' attribute added to the script tag that represents we are using the composition API approach.
  • (Line: 5-9) The 'reactive' type loads from 'vue' library. The 'reactive' type is appropriate for creating models for the form binding. Here we defined properties like 'name', 'location', and 'imageUrl' which can use for form binding.
  • (Line: 11-15) The 'addNewWonder' method contains logic to invoke the HTTP Post API call.
  • In vue to enable the form model binding we will use 'v-model' attribute. Each form field must be decorated with the 'v-model'.
  • (Line: 20) The '@submit.prevent' event raises on clicking the form submit button, but it won't reload the page since we configured the 'prevent' event. Here '@submit.prevent' is registered with 'addNewWonder' method.

Let's configure the 'Add' button in 'AllWonders.vue' component so that we can navigate from the 'AllWonders.vue' to  'AddWonders.vue'.

src/views/wonders/Allwonders.vue:


  1. <script setup>
  2. import { ref, onMounted } from "vue";
  3. import axios from "axios";
  4. const allWonders = ref([]);
  5. onMounted(() => {
  6. axios.get("http://localhost:3000/wonders").then((response) => {
  7. allWonders.value = response.data;
  8. </script>
  9. <template>
  10. <div class="container mt-3">
  11. <div class="row m-2">
  12. <div class="col col-md-4 offset-md-4">
  13. <RouterLink to="/add-wonder" class="btn btn-primary">Add</RouterLink>
  14. </div>
  15. </div>
  16. <div class="row row-cols-1 row-cols-md-3 g-4">
  17. <div class="col" v-for="item in allWonders" :key="item.id">
  18. <div class="card">
  19. <img :src="item.imageUrl" class="card-img-top" alt="..." />
  20. <div class="card-body">
  21. <h5 class="card-title">{{ item.name }}</h5>
  22. <p class="card-text">Location: {{ item.location }}</p>
  23. </div>
  24. </div>
  25. </div>
  26. </div>
  27. </div>
  28. </template>
  • (Line: 15) Added the 'RouterLink' its 'to' attribute configured with '/add-wonder' route.

(Step 1)

8.PNG
(Step 2)
9.PNG
(Step 3)
10.PNG

Create A 'EditWonders.vue' Component:

Let's create a new Vue component 'EditWornders.vue' in 'src/views/wonders'.
12.PNG

Now configure routing for the 'EditWonders.vue' component in 'router/index.js'.

src/router/index.js:


  1. import { createRouter, createWebHistory } from "vue-router";
  2. import AllWonders from "../views/wonders/AllWonders.vue";
  3. import AddWonders from "../views/wonders/AddWonders.vue";
  4. import EditWonders from "../views/wonders/EditWonders.vue";
  5. const router = createRouter({
  6. history: createWebHistory(import.meta.env.BASE_URL),
  7. routes: [
  8. path: "/",
  9. name: "home",
  10. component: AllWonders,
  11. path: "/add-wonder",
  12. name: "Add Wonder",
  13. component: AddWonders,
  14. path: "/edit-wonder/:id",
  15. name: "Edit Wonder",
  16. component: EditWonders,
  17. export default router;
  • (Line: 18-22) configured the 'edit-wonder/:id' route for the 'EditWonders.vue' component. Here ':id' is the dynamic placeholder where we define an item to edit the 'id' value in the route.

Implement Update Operation:

The update operation means invoking the HTTP PUT endpoint to update the existing item.
src/views/wonders/EditWonders.vue:


  1. <script setup>
  2. import axios from "axios";
  3. import { reactive, onMounted } from "vue";
  4. import { useRouter, useRoute } from "vue-router";
  5. let wonderToUpdate = reactive({
  6. id: 0,
  7. name: "",
  8. location: "",
  9. imageUrl: "",
  10. const router = useRouter();
  11. const route = useRoute();
  12. onMounted(() => {
  13. axios
  14. .get(`http://localhost:3000/wonders/${route.params.id}`)
  15. .then((response) => {
  16. wonderToUpdate.id = response.data.id;
  17. wonderToUpdate.name = response.data.name;
  18. wonderToUpdate.location = response.data.location;
  19. wonderToUpdate.imageUrl = response.data.imageUrl;
  20. const updateWonder = () => {
  21. axios.put(`http://localhost:3000/wonders/${route.params.id}`, wonderToUpdate).then(() => {
  22. router.push("/");
  23. </script>
  24. <template>
  25. <div class="container mt-4 w-50">
  26. <form @submit.prevent="updateWonder">
  27. <legend>Update Wonders</legend>
  28. <div class="mb-3">
  29. <label for="txtWonderName" class="form-label">Name</label>
  30. <input
  31. type="text"
  32. v-model="wonderToUpdate.name"
  33. class="form-control"
  34. id="txtWonderName"
  35. />
  36. </div>
  37. <div class="mb-3">
  38. <label for="txtLocation" class="form-label">Location</label>
  39. <input
  40. type="text"
  41. v-model="wonderToUpdate.location"
  42. class="form-control"
  43. id="txtLocation"
  44. />
  45. </div>
  46. <div class="mb-3">
  47. <label for="txtImageUrl" class="form-label">Image URL</label>
  48. <input
  49. type="text"
  50. v-model="wonderToUpdate.imageUrl"
  51. class="form-control"
  52. id="txtImageUrl"
  53. />
  54. </div>
  55. <button type="submit" class="btn btn-primary">Update</button>
  56. </form>
  57. </div>
  58. </template>
  • (Line: 1) The 'setup' attribute added to the script tag that represents we are using the composition API approach.
  • (Line: 6-11) The 'reactive' type loads from the 'vue' library. The 'reactive' type is appropriate for creating the model for the form binding in vue. Here we defined properties like 'id', 'name', 'location', and 'imageUrl' which can use for the form binding.
  • (Line: 13) The 'useRouter' loads from the 'vue-router' library that helps with navigation.
  • (Line: 14) The 'useRoute' loads from the 'vue-router' library that helps to read the URL parameters.
  • (Line; 16-25) The 'onMounted' is a Vue life-cycle method in which we invoke the get by 'id' endpoint to populate the item data on to our update form.
  • (Line: 27-31) Here we defined a method like 'updateWonder' that contains logic to invoke the HTTP PUT request to update the item.
  • In vue enable the form model binding we will use 'v-model' attribute. Each form field must be decorate with 'v-model'.
  • (Line: 35) The '@submit.prevent' event raises on clicking the form submit button, but it won't reload the page since we configured the 'prevent' event. Here '@submit.prevent' is registered with 'updateWonder' method.

Let's configure the 'Edit' button in 'AllWonders.vue' component. So that we can navigate from the 'AllWonders.vue' to 'EditWonders.vue'.

src/views/wonders/EditWonders.vue:


  1. <div class="card">
  2. <img :src="item.imageUrl" class="card-img-top" alt="..." />
  3. <div class="card-body">
  4. <h5 class="card-title">{{ item.name }}</h5>
  5. <p class="card-text">Location: {{ item.location }}</p>
  6. <router-link class="btn btn-primary" :to="`/edit-wonder/${item.id}`">Edit</router-link>
  7. </div>
  8. </div>
  • (Line: 6) The 'Routerlink' is configured as our Edit button. Here is our dynamically generated edit route adding at 'to' attribute.

(Step 1)

13.PNG
(Step 2)
14.PNG
(Step 3)
15.PNG

Create A 'ConfirmDeletePopup' Component:

Let's create 'ConfirmDeletePopup.vue' VueJS component in 'src/components' folder, this component can be used as global and its functionality is to show a popup for deleting any kind of item for your application.
src/components/ConfirmDeletePopup.vue:


  1. <script setup></script>
  2. <template>
  3. <div
  4. class="modal fade"
  5. id="deleteModal"
  6. tabindex="-1"
  7. aria-labelledby="exampleModalLabel"
  8. aria-hidden="true"
  9. <div class="modal-dialog">
  10. <div class="modal-content">
  11. <div class="modal-header">
  12. <h1 class="modal-title fs-5" id="exampleModalLabel">
  13. Delete Confirmation!
  14. </h1>
  15. <button
  16. type="button"
  17. class="btn-close"
  18. data-bs-dismiss="modal"
  19. aria-label="Close"
  20. ></button>
  21. </div>
  22. <div class="modal-body">Are you sure to delete this item</div>
  23. <div class="modal-footer">
  24. <button
  25. type="button"
  26. class="btn btn-secondary"
  27. data-bs-dismiss="modal"
  28. Close
  29. </button>
  30. <button
  31. type="button"
  32. @click="$emit('confirmdelete-click')"
  33. class="btn btn-danger"
  34. Confirm Delete
  35. </button>
  36. </div>
  37. </div>
  38. </div>
  39. </div>
  40. </template>
  • Here we added the bootstrap modal HTML content.
  • (Line: 5) Make sure to give 'id' attribute value fo the bootstrap modal.
  • (Line: 34) Here button click emits 'confirmdelete-click' which means we are trying to receive an event from the child component(ConfirmDeletePopup.vue) to the parent component(like 'AllWonders.vue'). To emit the event we have to use '$emit()'.

Implement Delete Operation:

The delete operation means invoking the HTTP Delete endpoint for deleting the item.
src/views/wonders/AllWonders.vue:


  1. <script setup>
  2. import { ref, onMounted } from "vue";
  3. import ConfirmDeletePopup from "../../components/ConfirmDeletePopup.vue";
  4. import axios from "axios";
  5. const allWonders = ref([]);
  6. const itemToDeleteId = ref([0]);
  7. let deleteModal;
  8. onMounted(() => {
  9. deleteModal = new window.bootstrap.Modal(
  10. document.getElementById("deleteModal"));
  11. axios.get("http://localhost:3000/wonders").then((response) => {
  12. allWonders.value = response.data;
  13. const openDeleteModal = (id) => {
  14. itemToDeleteId.value = id;
  15. deleteModal.show();
  16. const confirmDelete = () => {
  17. axios
  18. .delete(`http://localhost:3000/wonders/${itemToDeleteId.value}`)
  19. .then(() => {
  20. allWonders.value = allWonders.value.filter(
  21. (_) => _.id !== itemToDeleteId.value
  22. itemToDeleteId.value = 0;
  23. deleteModal.hide();
  24. </script>
  25. <template>
  26. <div class="container mt-3">
  27. <div class="row m-2">
  28. <div class="col col-md-4 offset-md-4">
  29. <RouterLink to="/add-wonder" class="btn btn-primary">Add</RouterLink>
  30. </div>
  31. </div>
  32. <div class="row row-cols-1 row-cols-md-3 g-4">
  33. <div class="col" v-for="item in allWonders" :key="item.id">
  34. <div class="card">
  35. <img :src="item.imageUrl" class="card-img-top" alt="..." />
  36. <div class="card-body">
  37. <h5 class="card-title">{{ item.name }}</h5>
  38. <p class="card-text">Location: {{ item.location }}</p>
  39. <router-link class="btn btn-primary" :to="`/edit-wonder/${item.id}`"
  40. >Edit</router-link
  41. > |
  42. <button
  43. type="button"
  44. @click="openDeleteModal(item.id)"
  45. class="btn btn-danger"
  46. Delete
  47. </button>
  48. </div>
  49. </div>
  50. </div>
  51. </div>
  52. </div>
  53. <ConfirmDeletePopup @confirmdelete-click="confirmDelete"></ConfirmDeletePopup>
  54. </template>
  • (Line: 7) Declared the variable like 'deleteModal'.
  • (Line: 10-11) Bootstrap instance assigned to the 'deleteModal'.Here we use the modal 'id' attribute value while creating the bootstrap instance.
  • (Line: 18-21) The 'openDeleteModal' method contains logic to show the delete confirmation modal on clicking the delete button. Here we set the item to delete 'id' value to 'itemToDelete' variable.
  • (Line: 23-33) The 'confirmDelete' method contains logic to invoke the delete 
  • API call. On successful deletion, we are going to update our 'allWonders' variable and then hide the bootstrap modal.
  • (Line: 52-58) Here we added the 'Delete' button and its click registered with the 'openDeleteModal' method.
  • (Line: 64)Rendered our 'ConfirmDeletePopup' component and registered with '@confirmdelete-click' which reads the button click from the child component(ConfirmDeletePopup.vue)
16.PNG

Support Me!
Buy Me A Coffee PayPal Me

Video Session:

Wrapping Up:

Hopefully, I think this article delivered some useful information on the Vue(3.0) CRUD sample. using I love to have your feedback, suggestions, and better techniques in the comment section below.

Refer:

Follow Me:


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK