vuetify-data-table-example-crud-app-feature-image

Vuetify data-table example with a CRUD App | v-data-table

In this tutorial, I will show you how to build Vuetify data-table example with a CRUD App to consume REST APIs, display and modify data using v-data-table.

More Practice:
Vue.js JWT Authentication with Vuex and Vue Router
Vuetify File Upload example
Vuetify Pagination (Server side) example

Fullstack:
Vue.js + Node.js + Express + MySQL example
Vue.js + Node.js + Express + PostgreSQL example
Vue.js + Node.js + Express + MongoDB example
Vue.js + Spring Boot + MySQL/PostgreSQL example
Vue.js + Spring Boot + MongoDB example
Vue.js + Django example


Vuetify data-table example Overview with CRUD App

We will build a Vue.js front-end Tutorial Application in that:

  • Each Tutorial has id, title, description, published status.
  • We can create, retrieve, update, delete Tutorials.
  • There is a Search bar for finding Tutorials by title.

Here are screenshots of our Vue.js 2 CRUD Application.

– Create a Tutorial:

vuetify-data-table-example-crud-app-add-item

– Retrieve all Tutorials:

vuetify-data-table-example-crud-app-retrieve-all

– Click on Edit button to update a Tutorial:

vuetify-data-table-example-crud-app-edit-item

On this Page, you can:

  • change status to Published using Publish button
  • delete the Tutorial using Delete button
  • update the Tutorial details with Update button

vuetify-data-table-example-crud-app-update-item

– Search Tutorials by title:

vuetify-data-table-example-crud-app-search-by

The introduction above is for Vue.js Client with assumption that we have a Server exporting REST APIs:

MethodsUrlsActions
POST/api/tutorialscreate new Tutorial
GET/api/tutorialsretrieve all Tutorials
GET/api/tutorials/:idretrieve a Tutorial by :id
PUT/api/tutorials/:idupdate a Tutorial by :id
DELETE/api/tutorials/:iddelete a Tutorial by :id
DELETE/api/tutorialsdelete all Tutorials
GET/api/tutorials?title=[keyword]find all Tutorials which title contains keyword

You can find step by step to build a Server like this in one of these posts:
Express, Sequelize & MySQL
Express, Sequelize & PostgreSQL
Express & MongoDb
Spring Boot & MySQL/PostgreSQL
Spring Boot & MongoDB
Spring Boot & Cassandra
Django & MySQL
Django & PostgreSQL
Django & MongoDB

All of them can work well with this Vue App.

Component Diagram with Vue Router & Axios

vuetify-data-table-example-crud-app-component-diagram

– The App component is a container with router-view. It has navbar that links to routes paths.

TutorialsList component gets and displays Tutorials.
Tutorial component has form for editing Tutorial’s details based on :id.
AddTutorial component has form for submission new Tutorial.

– These Components call TutorialDataService methods which use axios to make HTTP requests and receive responses.

Technology

  • vue: 2.6.11
  • vuetify: 2.2.11
  • vue-router: 3.4.3
  • axios: 0.20.0

Setup Vuetify File Upload Project

Open cmd at the folder you want to save Project folder, run command:
vue create vuetify-data-table-example

You will see some options, choose default (babel, eslint).

After the Vue project is created successfully, we import Vuetify with command: vue add vuetify.
The Console will show:

�📦  Installing vue-cli-plugin-vuetify...

+ vue-cli-plugin-vuetify@2.0.7
added 5 packages from 7 contributors and audited 1277 packages in 25.013s
found 0 vulnerabilities

✔  Successfully installed plugin: vue-cli-plugin-vuetify

? Choose a preset: Default (recommended)

�🚀  Invoking generator for vue-cli-plugin-vuetify...
�📦  Installing additional dependencies...

added 7 packages from 5 contributors and audited 1284 packages in 41.183s
found 0 vulnerabilities

⚓  Running completion hooks...

✔  Successfully invoked generator for plugin: vue-cli-plugin-vuetify
   The following files have been updated / added:

     src/assets/logo.svg
     src/plugins/vuetify.js
     vue.config.js
     package-lock.json
     package.json
     public/index.html
     src/App.vue
     src/components/HelloWorld.vue
     src/main.js

Open plugins/vuetify.js, you can see:

import Vue from 'vue';
import Vuetify from 'vuetify/lib';

Vue.use(Vuetify);

export default new Vuetify({
});

Then in main.js file, Vuetify object will added automatically to Vue App:

import Vue from 'vue'
import App from './App.vue'
import vuetify from './plugins/vuetify';

Vue.config.productionTip = false

new Vue({
  vuetify,
  render: h => h(App)
}).$mount('#app')

And vue.config.js:

module.exports = {
  transpileDependencies: ["vuetify"]
};

Project Structure

Let’s remove unnecessary folders and files, then create new ones like this:

vuetify-data-table-example-crud-app-project-structure

Let me explain it briefly.

package.json contains 3 main modules: vue, vue-router, axios.
– There are 3 components: TutorialsList, Tutorial, AddTutorial.
router.js defines routes for each component.
http-common.js initializes axios with HTTP base Url and headers.
TutorialDataService has methods for sending HTTP requests to the Apis.
vue.config.js configures port for this Vue Client.

You can also find a Bootstrap version for this Vue App in the tutorial:
Vue.js 2 CRUD Application with Vue Router & Axios

Add Vue Router to Vuetify CRUD App

– Run the command: npm install vue-router.

– In src folder, create router.js and define Router as following code:

import Vue from "vue";
import Router from "vue-router";

Vue.use(Router);

export default new Router({
  mode: "history",
  routes: [
    {
      path: "/",
      alias: "/tutorials",
      name: "tutorials",
      component: () => import("./components/TutorialsList")
    },
    {
      path: "/tutorials/:id",
      name: "tutorial-details",
      component: () => import("./components/Tutorial")
    },
    {
      path: "/add",
      name: "add",
      component: () => import("./components/AddTutorial")
    }
  ]
});

– Open src/main.js, then import router:

import Vue from 'vue'
import App from './App.vue'
import vuetify from './plugins/vuetify'
import router from './router'

Vue.config.productionTip = false

new Vue({
  router,
  vuetify,
  render: h => h(App)
}).$mount('#app')

Add App Bar and Router View to Vuetify App

Let’s open src/App.vue, this App component is the root container for our application, it will contain a v-app-bar.

<template>
  <v-app>
    <v-app-bar app dark>
      <div class="d-flex align-center mr-2">
        bezKoder
      </div>

      <v-btn to="/tutorials" text>
        Tutorials
      </v-btn>

      <v-btn to="/add" text>
        Add
      </v-btn>
    </v-app-bar>

    <v-main>
      <router-view />
    </v-main>
  </v-app>
</template>

<script>
export default {
  name: "app",
};
</script>

Initialize Axios for Vue.js 2 CRUD HTTP Client

Now we’re gonna install axios with command: npm install axios.
Then, under src folder, we create http-common.js file like this:

import axios from "axios";

export default axios.create({
  baseURL: "http://localhost:8080/api",
  headers: {
    "Content-type": "application/json"
  }
});

Remember to change the baseURL, it depends on REST APIs url that your Server configures.

Create Data Service

Our service will use axios from HTTP client above to send HTTP requests.

services/TutorialDataService.js

import http from "../http-common";

class TutorialDataService {
  getAll() {
    return http.get("/tutorials");
  }

  get(id) {
    return http.get(`/tutorials/${id}`);
  }

  create(data) {
    return http.post("/tutorials", data);
  }

  update(id, data) {
    return http.put(`/tutorials/${id}`, data);
  }

  delete(id) {
    return http.delete(`/tutorials/${id}`);
  }

  deleteAll() {
    return http.delete(`/tutorials`);
  }

  findByTitle(title) {
    return http.get(`/tutorials?title=${title}`);
  }
}

export default new TutorialDataService();

Create Vuetify Components

As I’ve said before, we have 3 components corresponding to 3 routes defined in Vue Router.

Add item Component

This component has a Form to submit new Tutorial with 2 fields: title & description. It calls TutorialDataService.create() method.

For input fields, we use v-text-field with rules for validation.

components/AddTutorial.vue

<template>
  <div class="submit-form mt-3 mx-auto">
    <p class="headline">Add Tutorial</p>

    <div v-if="!submitted">
      <v-form ref="form" lazy-validation>
        <v-text-field
          v-model="tutorial.title"
          :rules="[(v) => !!v || 'Title is required']"
          label="Title"
          required
        ></v-text-field>

        <v-text-field
          v-model="tutorial.description"
          :rules="[(v) => !!v || 'Description is required']"
          label="Description"
          required
        ></v-text-field>
      </v-form>

      <v-btn color="primary" class="mt-3" @click="saveTutorial">Submit</v-btn>
    </div>

    <div v-else>
      <v-card class="mx-auto">
        <v-card-title>
          Submitted successfully!
        </v-card-title>

        <v-card-subtitle>
          Click the button to add new Tutorial.
        </v-card-subtitle>

        <v-card-actions>
          <v-btn color="success" @click="newTutorial">Add</v-btn>
        </v-card-actions>
      </v-card>
    </div>
  </div>
</template>

<script>
import TutorialDataService from "../services/TutorialDataService";

export default {
  name: "add-tutorial",
  data() {
    return {
      tutorial: {
        id: null,
        title: "",
        description: "",
        published: false,
      },
      submitted: false,
    };
  },
  methods: {
    saveTutorial() {
      var data = {
        title: this.tutorial.title,
        description: this.tutorial.description,
      };

      TutorialDataService.create(data)
        .then((response) => {
          this.tutorial.id = response.data.id;
          console.log(response.data);
          this.submitted = true;
        })
        .catch((e) => {
          console.log(e);
        });
    },

    newTutorial() {
      this.submitted = false;
      this.tutorial = {};
    },
  },
};
</script>

<style>
.submit-form {
  max-width: 300px;
}
</style>

List of items Component using v-data-table

This component calls 3 TutorialDataService methods:

  • getAll()
  • findByTitle()
  • delete()
  • deleteAll()

This is where we use v-data-table for displaying tabular data. In addition to tutorial’s fields (title, description, status), we also have Actions column with edit & delete icon buttons.

components/TutorialsList.vue

<template>
  <v-row align="center" class="list px-3 mx-auto">
    <v-col cols="12" md="8">
      <v-text-field v-model="title" label="Search by Title"></v-text-field>
    </v-col>

    <v-col cols="12" md="4">
      <v-btn small @click="searchTitle">
        Search
      </v-btn>
    </v-col>

    <v-col cols="12" sm="12">
      <v-card class="mx-auto" tile>
        <v-card-title>Tutorials</v-card-title>

        <v-data-table
          :headers="headers"
          :items="tutorials"
          disable-pagination
          :hide-default-footer="true"
        >
          <template v-slot:[`item.actions`]="{ item }">
            <v-icon small class="mr-2" @click="editTutorial(item.id)">mdi-pencil</v-icon>
            <v-icon small @click="deleteTutorial(item.id)">mdi-delete</v-icon>
          </template>
        </v-data-table>

        <v-card-actions v-if="tutorials.length > 0">
          <v-btn small color="error" @click="removeAllTutorials">
            Remove All
          </v-btn>
        </v-card-actions>
      </v-card>
    </v-col>
  </v-row>
</template>

<script>
import TutorialDataService from "../services/TutorialDataService";
export default {
  name: "tutorials-list",
  data() {
    return {
      tutorials: [],
      title: "",
      headers: [
        { text: "Title", align: "start", sortable: false, value: "title" },
        { text: "Description", value: "description", sortable: false },
        { text: "Status", value: "status", sortable: false },
        { text: "Actions", value: "actions", sortable: false },
      ],
    };
  },
  methods: {
    retrieveTutorials() {
      TutorialDataService.getAll()
        .then((response) => {
          this.tutorials = response.data.map(this.getDisplayTutorial);
          console.log(response.data);
        })
        .catch((e) => {
          console.log(e);
        });
    },

    refreshList() {
      this.retrieveTutorials();
    },

    removeAllTutorials() {
      TutorialDataService.deleteAll()
        .then((response) => {
          console.log(response.data);
          this.refreshList();
        })
        .catch((e) => {
          console.log(e);
        });
    },

    searchTitle() {
      TutorialDataService.findByTitle(this.title)
        .then((response) => {
          this.tutorials = response.data.map(this.getDisplayTutorial);
          console.log(response.data);
        })
        .catch((e) => {
          console.log(e);
        });
    },

    editTutorial(id) {
      this.$router.push({ name: "tutorial-details", params: { id: id } });
    },

    deleteTutorial(id) {
      TutorialDataService.delete(id)
        .then(() => {
          this.refreshList();
        })
        .catch((e) => {
          console.log(e);
        });
    },

    getDisplayTutorial(tutorial) {
      return {
        id: tutorial.id,
        title: tutorial.title.length > 30 ? tutorial.title.substr(0, 30) + "..." : tutorial.title,
        description: tutorial.description.length > 30 ? tutorial.description.substr(0, 30) + "..." : tutorial.description,
        status: tutorial.published ? "Published" : "Pending",
      };
    },
  },
  mounted() {
    this.retrieveTutorials();
  },
};
</script>

<style>
.list {
  max-width: 750px;
}
</style>

– If you click on edit button of any Tutorial, the app will direct you to Tutorial page with url: /tutorials/:tutorialId.
delete button helps us to remove the Tutorial directly.

You can add Pagination to this Component, just follow instruction in the post:
Vuetify Pagination (Server side) example

Item details Component

For getting data & update, delete the Tutorial, this component will use 3 TutorialDataService methods:

  • get()
  • update()
  • delete()

components/Tutorial.vue

<template>
  <div v-if="currentTutorial" class="edit-form py-3">
    <p class="headline">Edit Tutorial</p>

    <v-form ref="form" lazy-validation>
      <v-text-field
        v-model="currentTutorial.title"
        :rules="[(v) => !!v || 'Title is required']"
        label="Title"
        required
      ></v-text-field>

      <v-text-field
        v-model="currentTutorial.description"
        :rules="[(v) => !!v || 'Description is required']"
        label="Description"
        required
      ></v-text-field>

      <label><strong>Status:</strong></label>
      {{ currentTutorial.published ? "Published" : "Pending" }}

      <v-divider class="my-5"></v-divider>

      <v-btn v-if="currentTutorial.published"
        @click="updatePublished(false)"
        color="primary" small class="mr-2"
      >
        UnPublish
      </v-btn>

      <v-btn v-else
        @click="updatePublished(true)"
        color="primary" small class="mr-2"
      >
        Publish
      </v-btn>

      <v-btn color="error" small class="mr-2" @click="deleteTutorial">
        Delete
      </v-btn>

      <v-btn color="success" small @click="updateTutorial">
        Update
      </v-btn>
    </v-form>

    <p class="mt-3">{{ message }}</p>
  </div>

  <div v-else>
    <p>Please click on a Tutorial...</p>
  </div>
</template>

<script>
import TutorialDataService from "../services/TutorialDataService";

export default {
  name: "tutorial",
  data() {
    return {
      currentTutorial: null,
      message: "",
    };
  },
  methods: {
    getTutorial(id) {
      TutorialDataService.get(id)
        .then((response) => {
          this.currentTutorial = response.data;
          console.log(response.data);
        })
        .catch((e) => {
          console.log(e);
        });
    },

    updatePublished(status) {
      var data = {
        id: this.currentTutorial.id,
        title: this.currentTutorial.title,
        description: this.currentTutorial.description,
        published: status,
      };

      TutorialDataService.update(this.currentTutorial.id, data)
        .then((response) => {
          this.currentTutorial.published = status;
          console.log(response.data);
        })
        .catch((e) => {
          console.log(e);
        });
    },

    updateTutorial() {
      TutorialDataService.update(this.currentTutorial.id, this.currentTutorial)
        .then((response) => {
          console.log(response.data);
          this.message = "The tutorial was updated successfully!";
        })
        .catch((e) => {
          console.log(e);
        });
    },

    deleteTutorial() {
      TutorialDataService.delete(this.currentTutorial.id)
        .then((response) => {
          console.log(response.data);
          this.$router.push({ name: "tutorials" });
        })
        .catch((e) => {
          console.log(e);
        });
    },
  },
  mounted() {
    this.message = "";
    this.getTutorial(this.$route.params.id);
  },
};
</script>

<style>
.edit-form {
  max-width: 300px;
  margin: auto;
}
</style>

Configure Port for Vuetify CRUD App

Because most of HTTP Server use CORS configuration that accepts resource sharing retricted to some sites or ports, so we also need to configure port for our App.

In project root folder, open vue.config.js file and add following config param:

module.exports = {
  ...
  devServer: {
    port: 8081
  }
}

We’ve set our app running at port 8081.

Run Vue App

You can run our App with command: npm run serve.
If the process is successful, open Browser with Url: http://localhost:8081/ and check it.

This Vue Client will work well with following back-end Rest APIs:
Express, Sequelize & MySQL
Express, Sequelize & PostgreSQL
Express & MongoDb
Spring Boot & MySQL/PostgreSQL
Spring Boot & MongoDB
Spring Boot & Cassandra
Django & MySQL
Django & PostgreSQL
Django & MongoDB

Conclusion

Today we’ve built a Vuetify data-table example with a CRUD Application successfully using Vue Router & Axios. Now we can consume REST APIs, display and modify data in a clean way. I hope you apply it in your project at ease.

If you want a Bootstrap version for this Vue App, it is here:
Vue.js 2 CRUD Application with Vue Router & Axios

Or you can add Pagination Component like this:

vuetify-pagination-server-side-pagination-component

Just visit this tutorial:
Vuetify Pagination (Server side) example

Happy learning, see you again!

Further Reading

Source Code

You can find the complete source code for this tutorial on Github.

Leave a Reply

Your email address will not be published. Required fields are marked *