Following up on the last post of AQAP Series, here's the complete create-read-update-delete (CRUD) app relying on Spring (Boot), Vue.js and Axios.
See it in action:
I didn't mention Thymeleaf because there's no changes to the pages served by the back-end on this post.
I'll illustrate the code using the Role entity, but as always the complete code and the app running is available at the end.
Without further ado...
Adding REST operations
We start adding two new operations on the RoleController.java
:
@PostMapping("roles")
public Role save(@RequestBody Role role) {
return roleRepository.save(role);
}
@DeleteMapping("roles/{id}")
public void get(@PathVariable Long id) {
roleRepository.deleteById(id);
}
The save
method takes care of both create
and update
operations. Spring is smart enough to update when there's an ID present and to create a new entity otherwise.
The Role Form
This is our HTML form now:
<form v-on:submit.prevent="postRole">
<div class="card mb-auto">
<div aria-controls="roleForm" aria-expanded="false" class="card-header" data-target="#roleForm"
data-toggle="collapse" id="formHeader" style="cursor: pointer">
<div class="float-left">New/Edit Role</div>
<div class="float-right">+</div>
</div>
<div class="card card-body collapse" id="roleForm">
<div class="form-group row">
<label for="roleName" class="col-sm-4 col-form-label">Role Name</label>
<input id="roleId" type="hidden" v-model="role_id">
<input id="roleName" class="form-control col-sm-8" placeholder="Role Name" type="text"
v-model="role_name"/>
</div>
<div class="form-group row">
<div class="col col-sm-4"></div>
<input class="btn btn-primary col col-sm-8" type="submit" value="Save">
</div>
</div>
</div>
</form>
Two things to notice here:
-
v-on:submit.prevent="postRole"
is a Vue.js tag to specify the method to run when submitting the form and to prevent the default behaviour of page reloading on submit. -
v-model
is another Vue.js tag. This binds an input with Vue.js data.
New Edit and Delete buttons
On the Actions
column of our HTML table, just add two new buttons:
<td>
<button class="btn btn-primary" v-on:click="editRole(role)">Edit</button>
<button class="btn btn-danger" v-on:click="deleteRole(role)">Delete</button>
</td>
Notice the same v-on
tag, but now with an action of click
. This binds the button click to a Vue.js method.
The Vue.js Magic... again.
Our Vue.js script is now a little scary:
<script>
var app = new Vue({
el: '#main',
data() {
return {
roles: null,
role_id: '',
role_name: '',
}
},
mounted(){
this.getRoles();
},
methods: {
getRoles: function () {
axios
.get("/api/v1/roles")
.then(response => (this.roles = response.data))
},
postRole: function (event) {
// Creating
if (this.role_id == '' || this.role_id == null) {
axios
.post("/api/v1/roles", {
"name": this.role_name,
})
.then(savedRole => {
this.roles.push(savedRole.data);
this.role_name = '';
this.role_id = '';
})
} else { // Updating
axios
.post("/api/v1/roles", {
"id": this.role_id,
"name": this.role_name,
})
.then(savedRole => {
this.getRoles();
this.role_name = '';
this.role_id = '';
})
}
},
editRole: function (role) {
this.role_id = role.id;
this.role_name = role.name;
document.getElementById('roleForm').setAttribute("class", document.getElementById('roleForm').getAttribute("class") + " show");
},
deleteRole: async function (role) {
await axios
.delete("/api/v1/roles/" + role.id);
this.getRoles();
}
},
})
</script>
But it's quite simple, actually. Let's explore what matters:
-
el: '#main'
specifies that Vue.js is going to operate on this HTML element id. In our case this isdiv
containing the form and table. - Inside
data()
we can specify variables that we are going to manipulate on the script and that the user may interact with. In our case notice that we have defined variables that represent the content of the form that the user interacts with. -
mounted()
is called when Vue.js is ready (mounted on the element specified inel
above). Here we call a methodgetRoles()
. This method requests data to the API and sets it to a variable that is used to create the table of contents (usingv-for
explained on the last post). -
methods
contains all the methods that interact with the API. Notice how they equate to the CRUD operations:-
getRoles
is theread
operation. -
postRole
is thecreate
operation. -
editRole
is theupdate
operation. -
deleteRole
is thedelete
operation.
-
The app
You can see the app running here (slightly modified since this is an ongoing analysis).
The repository and the aforementioned commits, also slightly modified, here.
brunodrugowick / spring-thymeleaf-vue-crud-example
Complete CRUD example project with Spring Boot, Thymeleaf, Vue.js and Axios.
AQAP Series
As Quickly As Possible (AQAP) is a series of quick posts on something I find interesting. I encourage (and take part on) the discussions on the comments to further explore the technology, library or code quickly explained here.
Image by Jason King por Pixabay
Top comments (3)
This is really helpful for me , thx!
Cool! Glad to be helpful!
Make sure to take a look at the next of the series when I add security, it's very important! Also, there's a trick to connect Spring's csrf protection and Vue.js scripts like that.
Let me know if you need help with that, I have a tutorial but in Portuguese. I can point to the code, though.
Here it goes: github.com/brunodrugowick/securing...
The trick is to add the Thymeleaf's csrf input but integrate the token to the Axios library using Vue.js $refs.