How To Develop Vue.js 3 CRUD App using a REST API

How To Develop Vue.js 3 CRUD App using a REST API

Avatar photoPosted by

Hi there, today we will be developing a Vue.js 3 CRUD app using a REST API. The four basic operations in any type of programming are CRUD. CRUD is an acronym for CREATE, READ, UPDATE, DELETE.

Vue JS or also called vue.js is an open-source JavaScript library used for building user interfaces(UI). It is one of the most popular JavaScript libraries for building the front end.

In this tutorial, we will be using the REST API available that is made by Binaryboxtus. you can check it out here.

Prerequisite:

  • node >= v16.10.0
  • @vue/cli

Tutorial Video:

Step 1: Create A Vue.js Project

First, select a folder that you want the Vue.js project to be created then execute this command on Terminal or CMD :

vue create vue-crud-app

make sure you select vue 3. Refer to the image below:

image 18 Binaryboxtuts

Step 2: Install packages

After creating a fresh vue.js project, go to the vue.js project folder and install these packages:

  • vue-router – a package used for routing and the official router for vue.js
  • bootstrap – a package of bootstrap framework
  • sweetalert2 -a package for pop-up boxes
  • axios – a promised-based HTTP library that is used to request to a server or API.
npm i bootstrap
npm i vue-router
npm i sweetalert2
npm i axios

Step 3: Create .env

After installing the packages, we will now create our .env file on our root directory. We will declare the API base URL and API Key in the .env file. Don’t forget to add it to the .gitignore file.

You can get the VUE_APP_API_KEY here. Click the “Reveal Key” button and copy the API key.

image 21 Binaryboxtuts

.env

VUE_APP_API_URL="https://mock-api.binaryboxtuts.com/"
VUE_APP_API_KEY="put_the_api_key_here"

Step 4: Routing and Components

We will now create the components, refer to the image below for the file structure.

image 17 Binaryboxtuts

You can create your own file structure but for this tutorial just follow along.

Let’s update the main.js file – We will add the axios base URL and add the API Key on the axios header.

src/main.js

import { createApp } from 'vue';
import App from './App.vue';
import axios from 'axios';
import 'bootstrap/dist/css/bootstrap.css';
import { createRouter, createWebHistory } from 'vue-router';
import ProjectList from './components/pages/ProjectList';
import ProjectCreate from './components/pages/ProjectCreate';
import ProjectEdit from './components/pages/ProjectEdit';
import ProjectShow from './components/pages/ProjectShow';
 
axios.defaults.baseURL = process.env.VUE_APP_API_URL
axios.interceptors.request.use(function (config) {
  config.headers['X-Binarybox-Api-Key'] = process.env.VUE_APP_API_KEY;
  return config;
});
 
const router = createRouter({
  history: createWebHistory(),
  routes: [
    { path: '/', component: ProjectList },
    { path: '/create', component: ProjectCreate },
    { path: '/edit/:id', component: ProjectEdit },
    { path: '/show/:id', component: ProjectShow },
  ],
});
 
createApp(App).use(router).mount('#app');

Let’s update the App.vue file – Let’s change all the code in it:

src/App.vue

<template>
  <div id="app">
    <router-view></router-view>
  </div>
</template>

<script>
export default {
  name: 'App',
};
</script>

Let’s create a file named LayoutDiv.vue – this will serve as a template.

src/components/LayoutDiv.vue

<template>
  <div class="container">
    <slot></slot>
  </div>
</template>

<script>

export default {
    name: 'LayoutDiv',
};
</script>

We will now create a directory src/components/pages. Inside the folder let’s create these files for our pages:

  • ProjectCreate.vue
  • ProjectEdit.vue
  • ProjectList.vue
  • ProjectShow.vue

src/components/pages/ProjectCreate.vue

<template>
  <layout-div>
    <h2 class="text-center mt-5 mb-3">Create New Project</h2>
    <div class="card">
        <div class="card-header">
            <router-link 
                class="btn btn-outline-info float-right"
                to="/">View All Projects
            </router-link>
        </div>
        <div class="card-body">
            <form>
                <div class="form-group">
                    <label htmlFor="name">Name</label>
                    <input 
                        v-model="project.name"
                        type="text"
                        class="form-control"
                        id="name"
                        name="name"/>
                </div>
                <div class="form-group">
                    <label htmlFor="description">Description</label>
                    <textarea 
                        v-model="project.description"
                        class="form-control"
                        id="description"
                        rows="3"
                        name="description"></textarea>
                </div>
                <button 
                    @click="handleSave()"
                    :disabled="isSaving"
                    type="button"
                    class="btn btn-outline-primary mt-3">
                    Save Project
                </button>
            </form>
        </div>
    </div>
  </layout-div>
</template>

<script>
import axios from 'axios';
import LayoutDiv from '../LayoutDiv.vue';
import Swal from 'sweetalert2'

export default {
  name: 'ProjectCreate',
  components: {
    LayoutDiv,
  },
  data() {
    return {
      project: {
        name: '',
        description: '',
      },
      isSaving:false,
    };
  },
  methods: {
    handleSave() {
        this.isSaving = true
        axios.post('/api/projects', this.project)
          .then(response => {
            Swal.fire({
                icon: 'success',
                title: 'Project saved successfully!',
                showConfirmButton: false,
                timer: 1500
            })
            this.isSaving = false
            this.project.name = ""
            this.project.description = ""
            return response
          })
          .catch(error => {
            this.isSaving = false
            Swal.fire({
                icon: 'error',
                title: 'An Error Occured!',
                showConfirmButton: false,
                timer: 1500
            })
            return error
          });
    },
  },
};
</script>

src/components/pages/ProjectEdit.vue

<template>
   <layout-div>
        <h2 class="text-center mt-5 mb-3">Edit Project</h2>
        <div class="card">
            <div class="card-header">
                <router-link 
                    class="btn btn-outline-info float-right"
                    to="/">View All Projects
                </router-link>
            </div>
            <div class="card-body">
                <form>
                    <div class="form-group">
                        <label htmlFor="name">Name</label>
                        <input 
                            v-model="project.name"
                            type="text"
                            class="form-control"
                            id="name"
                            name="name"/>
                    </div>
                    <div class="form-group">
                        <label htmlFor="description">Description</label>
                        <textarea 
                            v-model="project.description"
                            class="form-control"
                            id="description"
                            rows="3"
                            name="description"></textarea>
                    </div>
                    <button 
                        @click="handleSave()"
                        :disabled="isSaving"
                        type="button"
                        class="btn btn-outline-primary mt-3">
                        Save Project
                    </button>
                </form>
            </div>
        </div>
   </layout-div>
</template>

<script>
import axios from 'axios';
import LayoutDiv from '../LayoutDiv.vue';
import Swal from 'sweetalert2'

export default {
  name: 'ProjectEdit',
  components: {
    LayoutDiv,
  },
  data() {
    return {
      project: {
        name: '',
        description: '',
      },
      isSaving:false,
    };
  },
  created() {
    const id = this.$route.params.id;
    axios.get(`/api/projects/${id}`)
    .then(response => {
        let projectInfo = response.data
        this.project.name = projectInfo.name
        this.project.description = projectInfo.description
        return response
    })
    .catch(error => {
        Swal.fire({
            icon: 'error',
            title: 'An Error Occured!',
            showConfirmButton: false,
            timer: 1500
        })
        return error
    })
  },
  methods: {
    handleSave() {
        this.isSaving = true
        const id = this.$route.params.id;
        axios.patch(`/api/projects/${id}`, this.project)
          .then(response => {
            Swal.fire({
                icon: 'success',
                title: 'Project updated successfully!',
                showConfirmButton: false,
                timer: 1500
            })
            this.isSaving = false
            this.project.name = ""
            this.project.description = ""
            return response
          })
          .catch(error => {
            this.isSaving = false
            Swal.fire({
                icon: 'error',
                title: 'An Error Occured!',
                showConfirmButton: false,
                timer: 1500
            })
            return error
          });
    },
  },
};
</script>

src/components/pages/ProjectList.vue

<template>
  <layout-div>
        <div class="container">
            <h2 class="text-center mt-5 mb-3">Project Manager</h2>
            <div class="card">
                <div class="card-header">
                    <router-link to="/create"
                        class="btn btn-outline-primary"
                        >Create New Project
                    </router-link>
                </div>
                <div class="card-body">
            
                    <table class="table table-bordered">
                        <thead>
                            <tr>
                                <th>Name</th>
                                <th>Description</th>
                                <th width="240px">Action</th>
                            </tr>
                        </thead>
                        <tbody>
                            
                            <tr v-for="project in projects" :key="project.id">
                                <td>{{project.name}}</td>
                                <td>{{project.description}}</td>
                                <td>
                                    <router-link :to="`/show/${project.id}`" class="btn btn-outline-info mx-1">Show</router-link>
                                    <router-link :to="`/edit/${project.id}`" class="btn btn-outline-success mx-1">Edit</router-link>
                                    <button 
                                        @click="handleDelete(project.id)"
                                        className="btn btn-outline-danger mx-1">
                                        Delete
                                    </button>
                                </td>
                            </tr>
                                
                        </tbody>
                    </table>
                </div>
            </div>
        </div>
    </layout-div>
</template>

<script>
import axios from 'axios';
import LayoutDiv from '../LayoutDiv.vue';
import Swal from 'sweetalert2'

export default {
  name: 'ProjectList',
  components: {
    LayoutDiv,
  },
  data() {
    return {
      projects:[]
    };
  },
  created() {
    this.fetchProjectList();
  },
  methods: {
    fetchProjectList() {
      axios.get('/api/projects')
        .then(response => {
            this.projects = response.data;
            return response
        })
        .catch(error => {
          return error
        });
    },
    handleDelete(id){
        Swal.fire({
            title: 'Are you sure?',
            text: "You won't be able to revert this!",
            icon: 'warning',
            showCancelButton: true,
            confirmButtonColor: '#3085d6',
            cancelButtonColor: '#d33',
            confirmButtonText: 'Yes, delete it!'
          }).then((result) => {
            if (result.isConfirmed) {
                axios.delete(`/api/projects/${id}`)
                .then( response => {
                    Swal.fire({
                        icon: 'success',
                        title: 'Project deleted successfully!',
                        showConfirmButton: false,
                        timer: 1500
                    })
                    this.fetchProjectList();
                    return response
                })
                .catch(error => {
                    Swal.fire({
                         icon: 'error',
                        title: 'An Error Occured!',
                        showConfirmButton: false,
                        timer: 1500
                    })
                    return error
                });
            }
          })
    }
  },
};
</script>

src/components/pages/ProjectShow.vue

<template>
   <layout-div>
        <h2 class="text-center mt-5 mb-3">Show Project</h2>
        <div class="card">
            <div class="card-header">
                <router-link 
                    class="btn btn-outline-info float-right"
                    to="/">View All Projects
                </router-link>
            </div>
            <div class="card-body">
                <b className="text-muted">Name:</b>
                <p>{{project.name}}</p>
                <b className="text-muted">Description:</b>
                <p>{{project.description}}</p>
            </div>
        </div>
   </layout-div>
</template>

<script>
import axios from 'axios';
import LayoutDiv from '../LayoutDiv.vue';
import Swal from 'sweetalert2'

export default {
  name: 'ProjectShow',
  components: {
    LayoutDiv,
  },
  data() {
    return {
      project: {
        name: '',
        description: '',
      },
      isSaving:false,
    };
  },
  created() {
    const id = this.$route.params.id;
    axios.get(`/api/projects/${id}`)
    .then(response => {
        let projectInfo = response.data
        this.project.name = projectInfo.name
        this.project.description = projectInfo.description
        return response
    })
    .catch(error => {
        Swal.fire({
            icon: 'error',
            title: 'An Error Occured!',
            showConfirmButton: false,
            timer: 1500
        })
        return error
    })
  },
  methods: {
    
  },
};
</script>

Step 5: Run the app

We’re all done, what is left is to run the app.

npm run serve

Open this URL:

http://localhost:8080/

Screenshots:

Index Page

How To Develop ReactJS CRUD App using a REST API

Create Page

How To Develop ReactJS CRUD App using a REST API

Edit Page

How To Develop ReactJS CRUD App using a REST API

Show Page

How To Develop ReactJS CRUD App using a REST API