Vue Typescript example: Build a CRUD Application

In this tutorial, I will show you how to build a Vue.js Typescript CRUD Application to consume REST APIs, display and modify data using Vue, Vue Router, Axios & Bootstrap.

More Practice:
Vue/Vuex Typescript: JWT Authentication example
Vue File Upload example using Axios

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 example
Vue.js + Spring Boot + PostgreSQL example
Vue.js + Spring Boot + MongoDB example
Vue.js + Django example

Vue 3 Typescript version at:
Vue 3 Typescript example with Axios: Build CRUD App


Overview of Vue Typescript CRUD Application

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 Typescript CRUD Application.

– Create an object:

vue-typescript-example-crud-create

– Retrieve all objects:

vue-typescript-example-crud-retrieve

– Click on Edit button to update an object:

vue-typescript-example-crud-retrieve-one

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

vue-typescript-example-crud-update

– Search Tutorials by title:

vue-typescript-example-crud-find-by-field

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

Methods Urls Actions
POST /api/tutorials create new Tutorial
GET /api/tutorials retrieve all Tutorials
GET /api/tutorials/:id retrieve a Tutorial by :id
PUT /api/tutorials/:id update a Tutorial by :id
DELETE /api/tutorials/:id delete a Tutorial by :id
DELETE /api/tutorials delete 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, Sequelize & SQL Server
Express & MongoDb
Spring Boot & MySQL
Spring Boot & PostgreSQL
Spring Boot & MongoDB
Spring Boot & SQL Server
Spring Boot & H2
Spring Boot & Cassandra
Spring Boot & Oracle
Python/Django & MySQL
Python/Django & PostgreSQL
Python/Django & MongoDB

All of them can work well with this Vue Typescript App.

Vue Typescript App Component Diagram

vue-typescript-example-crud-overview

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

TutorialsList component gets and displays Tutorials.
Tutorial (TutorialDetails) 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
  • vue-router: 3.1.6
  • axios: 0.19.2
  • vue-class-component 7.2.3
  • vue-property-decorator 8.4.1

Project Structure

vue-typescript-example-crud-project-structure-new

Let me explain it briefly.

package.json contains main modules: vue, vue-router, vue-class-component, axios.
types/Tutorial.ts exports Tutorial interface.
– There are 3 components: TutorialsList, TutorialDetails, AddTutorial.
router/index.ts defines routes for each component.
http-common.ts 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.

Setup Vue.js Typescript Project

Open cmd at the folder you want to save Project folder, run command:
vue create vue-typescript-crud

You will see some options, choose Manually select features.
Select the following options:

vue-typescript-example-crud-create-app

Then configure the project like this:

? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, TS
? Use class-style component syntax? Yes
? Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)? Yes
? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? In package.json
? Save this as a preset for future projects? (y/N) No

After the process is done. We create new folders and files like the following tree:


public

index.html

src

components

AddTutorial.vue

TutorialDetails.vue

TutorialsList.vue

services

TutorialDataService.ts

types

Tutorial.ts

App.vue

main.ts

package.json


Open public/index.html, add bootstrap inside <head> tag:

<!DOCTYPE html>
<html lang="en">
  <head>
    ...
    <title>Vue Typescript CRUD</title>
    <link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
  </head>
  <body>
    ...
  </body>
</html>

Add Vue Router to Vue Typescript App

– Run the command: npm install vue-router.

– In src/router folder, create index.ts and define Router as following code:

import Vue from "vue";
import VueRouter, { RouteConfig } from "vue-router";

Vue.use(VueRouter);

const routes: Array<RouteConfig> = [
  {
    path: "/",
    alias: "/tutorials",
    name: "tutorials",
    component: () => import("../components/TutorialsList.vue")
  },
  {
    path: "/tutorials/:id",
    name: "tutorial-details",
    component: () => import("../components/TutorialDetails.vue")
  },
  {
    path: "/add",
    name: "add",
    component: () => import("../components/AddTutorial.vue")
  }
];

const router = new VueRouter({
  mode: "history",
  base: process.env.BASE_URL,
  routes
});

export default router;

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

import Vue from "vue";
import App from "./App.vue";
import router from "./router";

Vue.config.productionTip = false;

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

Add Navbar and Router View to Vue Typescript App

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

<template>
  <div id="app">
    <nav class="navbar navbar-expand navbar-dark bg-dark">
      <a href="#" class="navbar-brand">bezKoder</a>
      <div class="navbar-nav mr-auto">
        <li class="nav-item">
          <a href="/tutorials" class="nav-link">Tutorials</a>
        </li>
        <li class="nav-item">
          <a href="/add" class="nav-link">Add</a>
        </li>
      </div>
    </nav>

    <div class="container mt-3">
      <router-view />
    </div>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from "vue-property-decorator";

@Component
export default class App extends Vue {}
</script>

Initialize Axios for Vue Typescript CRUD HTTP Client

Let’s install axios with command: npm install axios.
Then, under src folder, we create http-common.ts 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.

For more details about ways to use Axios, please visit:
Axios request: Get/Post/Put/Delete example

Create Data Service

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

services/TutorialDataService.ts

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

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

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

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

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

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

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

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

export default new TutorialDataService();

Create Tutorial interface

Open types/Tutorial.ts and define Tutorial interface:

export default interface Tutorial {
  id: null;
  title: string;
  description: string;
  published: boolean;
}

Create Vue Components

As I’ve said before, there are 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.

components/AddTutorial.vue

<template>
  <div class="submit-form">
    <div v-if="!submitted">
      <div class="form-group">
        <label for="title">Title</label>
        <input
          type="text"
          class="form-control"
          id="title"
          required
          v-model="tutorial.title"
          name="title"
        />
      </div>

      <div class="form-group">
        <label for="description">Description</label>
        <input
          class="form-control"
          id="description"
          required
          v-model="tutorial.description"
          name="description"
        />
      </div>

      <button @click="saveTutorial" class="btn btn-success">Submit</button>
    </div>

    <div v-else>
      <h4>You submitted successfully!</h4>
      <button class="btn btn-success" @click="newTutorial">Add</button>
    </div>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
import TutorialDataService from "@/services/TutorialDataService";
import Tutorial from "@/types/Tutorial";

@Component
export default class AddTutorial extends Vue {
  private tutorial: Tutorial = {
    id: null,
    title: "",
    description: "",
    published: false,
  };

  private submitted: boolean = false;

  saveTutorial() {
    let 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 = {} as Tutorial;
  }
}
</script>

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

List of items Component

This component calls 3 TutorialDataService methods:

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

components/TutorialsList.vue

<template>
  <div class="list row">
    <div class="col-md-8">
      <div class="input-group mb-3">
        <input
          type="text"
          class="form-control"
          placeholder="Search by title"
          v-model="title"
        />
        <div class="input-group-append">
          <button
            class="btn btn-outline-secondary"
            type="button"
            @click="searchTitle"
          >
            Search
          </button>
        </div>
      </div>
    </div>
    <div class="col-md-6">
      <h4>Tutorials List</h4>
      <ul class="list-group">
        <li
          class="list-group-item"
          :class="{ active: index == currentIndex }"
          v-for="(tutorial, index) in tutorials"
          :key="index"
          @click="setActiveTutorial(tutorial, index)"
        >
          {{ tutorial.title }}
        </li>
      </ul>

      <button class="m-3 btn btn-sm btn-danger" @click="removeAllTutorials">
        Remove All
      </button>
    </div>
    <div class="col-md-6">
      <div v-if="currentTutorial.id">
        <h4>Tutorial</h4>
        <div>
          <label><strong>Title:</strong></label> {{ currentTutorial.title }}
        </div>
        <div>
          <label><strong>Description:</strong></label>
          {{ currentTutorial.description }}
        </div>
        <div>
          <label><strong>Status:</strong></label>
          {{ currentTutorial.published ? "Published" : "Pending" }}
        </div>

        <a
          class="badge badge-warning"
          :href="'/tutorials/' + currentTutorial.id"
        >
          Edit
        </a>
      </div>
      <div v-else>
        <br />
        <p>Please click on a Tutorial...</p>
      </div>
    </div>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
import TutorialDataService from "@/services/TutorialDataService";
import Tutorial from "@/types/Tutorial";

@Component
export default class TutorialsList extends Vue {
  private tutorials: Tutorial[] = [];
  private currentTutorial = {} as Tutorial;
  private currentIndex: number = -1;
  private title: string = "";

  retrieveTutorials() {
    TutorialDataService.getAll()
      .then((response) => {
        this.tutorials = response.data;
        console.log(response.data);
      })
      .catch((e) => {
        console.log(e);
      });
  }

  refreshList() {
    this.retrieveTutorials();
    this.currentTutorial = {} as Tutorial;
    this.currentIndex = -1;
  }

  setActiveTutorial(tutorial: Tutorial, index: number) {
    this.currentTutorial = tutorial;
    this.currentIndex = index;
  }

  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;
        console.log(response.data);
      })
      .catch((e) => {
        console.log(e);
      });
  }

  mounted() {
    this.retrieveTutorials();
  }
}
</script>

<style scoped>
.list {
  text-align: left;
  max-width: 750px;
  margin: auto;
}
</style>

If you click on Edit button of any Tutorial, the app will direct you to Tutorial page with url: /tutorials/:tutorialId.

Item details Component

This component will use 3 TutorialDataService methods for getting data & update, delete the Tutorial:

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

components/TutorialDetails.vue

<template>
  <div v-if="currentTutorial.id" class="edit-form">
    <h4>Tutorial</h4>
    <form>
      <div class="form-group">
        <label for="title">Title</label>
        <input
          type="text"
          class="form-control"
          id="title"
          v-model="currentTutorial.title"
        />
      </div>
      <div class="form-group">
        <label for="description">Description</label>
        <input
          type="text"
          class="form-control"
          id="description"
          v-model="currentTutorial.description"
        />
      </div>

      <div class="form-group">
        <label><strong>Status:</strong></label>
        {{ currentTutorial.published ? "Published" : "Pending" }}
      </div>
    </form>

    <button
      class="badge badge-primary mr-2"
      v-if="currentTutorial.published"
      @click="updatePublished(false)"
    >
      UnPublish
    </button>
    <button
      v-else
      class="badge badge-primary mr-2"
      @click="updatePublished(true)"
    >
      Publish
    </button>

    <button class="badge badge-danger mr-2" @click="deleteTutorial">
      Delete
    </button>

    <button type="submit" class="badge badge-success" @click="updateTutorial">
      Update
    </button>
    <p>{{ message }}</p>
  </div>

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

<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
import TutorialDataService from "@/services/TutorialDataService";
import Tutorial from "@/types/Tutorial";

@Component
export default class TutorialDetails extends Vue {
  private currentTutorial = {} as Tutorial;
  private message: string = "";

  getTutorial(id: string) {
    TutorialDataService.get(id)
      .then((response) => {
        this.currentTutorial = response.data;
        console.log(response.data);
      })
      .catch((e) => {
        console.log(e);
      });
  }

  updatePublished(status: boolean) {
    let 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 scoped>
.edit-form {
  max-width: 300px;
  margin: auto;
}
</style>

Configure Port for Vue Typescript CRUD App

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

In project root folder, create vue.config.js file with following content:

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

We’ve set our app running at port 8081.

Run Vue Typescript 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, Sequelize & SQL Server
Express & MongoDb
Spring Boot & MySQL
Spring Boot & PostgreSQL
Spring Boot & MongoDB
Spring Boot & SQL Server
Spring Boot & H2
Spring Boot & Cassandra
Spring Boot & Oracle
Python/Django & MySQL
Python/Django & PostgreSQL
Python/Django & MongoDB

Conclusion

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

You can also know how to add Token based Authentication into Vue/Vuex Typescript App with this post:
Vue/Vuex Typescript: JWT Authentication example

Happy learning, see you again!

Further Reading

For more details about ways to use Axios, please visit:
Axios request: Get/Post/Put/Delete example

Source Code

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

Vue 3 Typescript version at:
Vue 3 Typescript example with Axios: Build CRUD App

2 thoughts to “Vue Typescript example: Build a CRUD Application”

Comments are closed to reduce spam. If you have any question, please send me an email.