In this tutorial, I will show you how to build a Vue.js 3 CRUD example to consume REST APIs, display and modify data using Axios and Vue Router.
More Practice:
– Vue 3 Authentication with JWT, Vuex, Axios and Vue Router
– 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
Typescript version at:
Vue 3 Typescript example with Axios: Build CRUD App
Vuetify version: Vuetify data-table example with a CRUD App | v-data-table
Serverless with Firebase:
– Vue Firebase Realtime Database CRUD example
– Vue Firebase Firestore CRUD example
Contents
- Overview of Vue.js 3 CRUD example
- Vue.js 3 Component Diagram with Vue Router & Axios
- Technology
- Project Structure
- Setup Vue 3 Project
- Add Bootstrap to Vue 3 CRUD example
- Add Vue Router to Vue 3 CRUD example
- Add Navbar and Router View to Vue 3 CRUD example
- Initialize Axios for Vue 3 CRUD HTTP Client
- Create Data Service
- Create Vue 3 Components
- Configure Port for Vue 3 CRUD example
- Run Vue.js 3 CRUD example
- Conclusion
- Further Reading
- Source Code
Overview of Vue.js 3 CRUD example
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 3 CRUD Application.
– Create a Tutorial:
– Retrieve all Tutorials:
– Click on Edit button to update a Tutorial:
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
– Search Tutorials by title:
The introduction above is for Vue 3 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 App.
Vue.js 3 Component Diagram with Vue Router & Axios
– 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 3
- vue-router 4
- axios 0.21.1
- bootstrap 4
Project Structure
Let me explain it briefly.
– package.json contains 4 main modules: vue
, vue-router
, axios
, bootstrap
.
– 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.
Setup Vue 3 Project
Open cmd at the folder you want to save Project folder, run command:
vue create vue-3-crud
You will see some options, choose Default ([Vue 3] babel, eslint).
After the process is done. We create new folders and files like the following tree:
public
src
components
AddTutorial.vue
Tutorial.vue
TutorialsList.vue
services
TutorialDataService.js
App.vue
main.js
package.json
Add Bootstrap to Vue 3 CRUD example
Run command: npm install [email protected] jquery popper.js
.
Open src/main.js and import Bootstrap as following-
import { createApp } from 'vue'
import App from './App.vue'
import 'bootstrap'
import 'bootstrap/dist/css/bootstrap.min.css'
...
Add Vue Router to Vue 3 CRUD example
– Run the command: npm install vue-router@4
.
– In src folder, create router.js and define Router
as following code:
import { createWebHistory, createRouter } from "vue-router";
const 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")
}
];
const router = createRouter({
history: createWebHistory(),
routes,
});
export default router;
We create the routes
as an array, each route has:
path
: the URL path where this route can be found.name
: optional name to use when we link to this route.component
: component to load when this route is called.
We also use createWebHistory
to switch from using hash to history
mode inside the browser, using the HTML5 history API.
– Open src/main.js and import the router in our application:
...
import router from './router'
createApp(App).use(router).mount('#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">
<router-link to="/" class="navbar-brand">bezKoder</router-link>
<div class="navbar-nav mr-auto">
<li class="nav-item">
<router-link to="/tutorials" class="nav-link">Tutorials</router-link>
</li>
<li class="nav-item">
<router-link to="/add" class="nav-link">Add</router-link>
</li>
</div>
</nav>
<div class="container mt-3">
<router-view />
</div>
</div>
</template>
<script>
export default {
name: "app"
};
</script>
Initialize Axios for Vue 3 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.
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.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 Vue 3 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.
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>
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;
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">
<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>
<router-link :to="'/tutorials/' + currentTutorial.id" class="badge badge-warning">Edit</router-link>
</div>
<div v-else>
<br />
<p>Please click on a Tutorial...</p>
</div>
</div>
</div>
</template>
<script>
import TutorialDataService from "../services/TutorialDataService";
export default {
name: "tutorials-list",
data() {
return {
tutorials: [],
currentTutorial: null,
currentIndex: -1,
title: ""
};
},
methods: {
retrieveTutorials() {
TutorialDataService.getAll()
.then(response => {
this.tutorials = response.data;
console.log(response.data);
})
.catch(e => {
console.log(e);
});
},
refreshList() {
this.retrieveTutorials();
this.currentTutorial = null;
this.currentIndex = -1;
},
setActiveTutorial(tutorial, index) {
this.currentTutorial = tutorial;
this.currentIndex = tutorial ? index : -1;
},
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;
this.setActiveTutorial(null);
console.log(response.data);
})
.catch(e => {
console.log(e);
});
}
},
mounted() {
this.retrieveTutorials();
}
};
</script>
<style>
.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
.
You can add Pagination to this Component, just follow instruction in the post:
Vue Pagination with Axios and API (Server Side pagination) 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">
<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>
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 => {
console.log(response.data);
this.currentTutorial.published = status;
this.message = 'The status was updated successfully!';
})
.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 Vue 3 CRUD example
Because most of HTTP Server use CORS configuration that accepts resource sharing restricted 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.js 3 CRUD example
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.js 3 CRUD example successfully with Axios and Vue Router. Now we can consume REST APIs, display and modify data in a clean way. I hope you apply it in your project at ease.
There is Typescript version at:
Vue 3 Typescript example with Axios: Build CRUD App
Or you can add Pagination Component:
Vue Pagination with Axios and API example
Or Vuetify version: Vuetify data-table example with a CRUD App | v-data-table
Happy learning, see you again!
Further Reading
For more details about ways to use Axios, please visit:
Axios request: Get/Post/Put/Delete example
Fullstack CRUD App:
- Vue.js + Node.js + Express + MySQL
- Vue.js + Node.js + Express + PostgreSQL
- Vue.js + Node.js + Express + MongoDB
- Vue.js + Spring Boot + MySQL
- Vue.js + Spring Boot + PostgreSQL example
- Vue.js + Spring Boot + MongoDB
- Vue.js + Django Rest Framework
Integration:
– Integrate Vue.js with Spring Boot
– Integrate Vue App with Node.js Express
Serverless with Firebase:
– Vue Firebase Realtime Database CRUD example
– Vue Firebase Firestore CRUD example
Source Code
You can find the complete source code for this tutorial on Github.
Typescript version at:
Vue 3 Typescript example with Axios: Build CRUD App
Security: Vue 3 Authentication with JWT, Vuex, Axios and Vue Router
Hello,
So I followed the tutorial all the way through and double checked. When I run the Vue frontend, it will only run on localhost:8082 no matter what I change do to the server running on 8081. What do I do?
Thank you
Hi, maybe the port 8081 is running another application.
Hi,
I followed your guide and it’s working fine except for 1 part. I used a local Django Rest Framework as a Server and I had issue with CORS: Reason: CORS header ‘Access-Control-Allow-Origin’ missing
If anyone is having the same issue this stackoverflow answer will fix it (I copied it below too):
https://stackoverflow.com/questions/56916448/access-control-allow-origin-issue-in-vue-js-and-django
pip install django-cors-headers
Then in settings.py:
INSTALLED_APPS = [
…
‘corsheaders’,
…
]
Along with:
CORS_ORIGIN_ALLOW_ALL=True
And add this middleware ‘corsheaders.middleware.CorsMiddleware’
Thanks for the tutorial and keep it coming !
This is perfect tutorial.
You did not use the vue 3 composition API
Hi, I will write the tutorial when having time.
Hello, thanks for your tutorial, just one question, how can you handle validation from server side in vuejs? I’m using Django rest, and I want to retrieve the errors from serializer
thanks in advance
Hello, I followed your tutorial and it worked mostly fine, but I have a problem with the “Edit” button in Tutorials List, and the 3 button (Publish/Unpublished, Delete, Update) in Tutorial.
The “Edit” button is transparent until I hover above it, and even then it showed like a hyperlink button (blue with underline). The 3 button in Tutorial does show, but they’re all colored gray with white font. Please help me, why does this happen and how to fix it?
Additional info:
– There is an error before, when compiling, telling me that a dependency was not found: @popperjs/core. So I installed that and uninstalled popper.js
– Also another warning that bootstrap requires a peer of postcss, so I installed that too.
Hi, you need to use Bootstrap 4 for this tutorial. I guess you used version 5. 🙂
Ah, yes, I did. Thanks for the help, you have an amazingly thorough and complete tutorial, so thanks a lot for that too!
Dear Sir,
I am unable to retrieve tutorial data and display in Vue.js Page. Please Help.I am using Vue3.js.I am a novice.
Thanks
Raichand
Hi, which backend did you use? Kindly show your web browser console log.
How do we connect the MYSQL database?
Hi, Vue is just a Client, you need a server to do this. Kindly read the backend tutorials that I mentioned above 🙂
Thank you
I logged in as admin
http://127.0.0.1:8000/admin/
Added two records. Those are inserted in Tutorials_tutorial Table.