
最近流行りの vue.js を Spring boot と組み合わせてWebアプリを作ってみます。
参考したサイトはこちら。
≫ [Full-stack] Spring Boot + Vue.js: CRUD example
参考にしたサイトでは Tutorial をエンティティにしてますが、ここでは Book をエンティティとする bookshelf アプリケーションにしてみます。
まずは、Spring bootのプロジェクトから作成していきます。
Spring bootのプロジェクトを作成する
ファイルメニューから「新規Springスタータープロジェクト」を選択して、プロジェクトを作成します。

依存関係は、Spring Data JPA と H2 Database と Spring Web を選択しておきます。

依存しているライブラリが自動的にダウンロードされるまで、しばらく待ちます。
プロジェクト・エクスプローラーの表示が以下のように変化したら準備完了です。

src/main/resources にある application.properties ファイルを開き、H2データベースにアクセスするための設定をします。
spring.datasource.url=jdbc:h2:mem:testdb spring.datasource.driverClassName=org.h2.Driver spring.datasource.username=sa spring.datasource.password= spring.jpa.show-sql=true spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect spring.jpa.hibernate.ddl-auto=update spring.h2.console.enabled=true
Bookエンティティを作成する
新規-クラスでBookクラスを作成します。
package jp.bookshelf;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
@Column
private String title;
@Column
private String author;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
}
BookRepositoryを作成する
次にリポジトリインタフェースを作ります。
新規-インタフェースでBookRepositoryインタフェースを作成します。
拡張インタフェースで JpaRepository
package jp.bookshelf;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
public interface BookRepository extends JpaRepository<Book, Long> {
List<Book> findByTitle(String title);
List<Book> findByAuthor(String author);
}
BookControllerを作成する
新規-クラスでBookControllerクラスを作成します。
package jp.bookshelf;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@CrossOrigin(origins = "http://localhost:8081")
@RestController
@RequestMapping("/api")
public class BookController {
@Autowired
BookRepository bookRepository;
@GetMapping("/books")
public ResponseEntity<List<Book>> getBooks(@RequestParam(required = false) String title) {
List<Book> list = bookRepository.findByTitle(title);
return new ResponseEntity<List<Book>>(list, HttpStatus.OK);
}
@GetMapping("/books/{id}")
public ResponseEntity<Book> getBookById(@PathVariable("id") Long id) {
Optional<Book> data = bookRepository.findById(id);
if (data.isPresent()) {
return new ResponseEntity<Book>(data.get(), HttpStatus.OK);
}
return new ResponseEntity<Book>(HttpStatus.NOT_FOUND);
}
@PostMapping("/books")
public ResponseEntity<Book> createBook(@RequestBody Book book) {
Book b = bookRepository.saveAndFlush(book);
return new ResponseEntity<Book>(b, HttpStatus.OK);
}
@PutMapping("/books/{id}")
public ResponseEntity<Book> updateBook(@RequestBody Book book) {
book = bookRepository.saveAndFlush(book);
return new ResponseEntity<Book>(book, HttpStatus.OK);
}
@DeleteMapping("/books")
public ResponseEntity<HttpStatus> deleteBook(@PathVariable("id") long id) {
bookRepository.deleteById(id);
return new ResponseEntity<HttpStatus>(HttpStatus.OK);
}
}
フロントエンドを vue.js で作成する
次は、フロントエンドを vue.js で作ってみましょう。
参考サイトでは、以下のコマンドで vue プロジェクトを作れと言ってます。
% vue create vue-js-client-crud
これは、vue CLI だそうです。
インストールしないといけないようです。
% npm install -g vue-cli npm WARN deprecated har-validator@5.1.5: this library is no longer supported npm WARN deprecated uuid@3.4.0: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details. npm WARN deprecated request@2.88.2: request has been deprecated, see https://github.com/request/request/issues/3142 npm WARN deprecated coffee-script@1.12.7: CoffeeScript on NPM has moved to "coffeescript" (no hyphen) npm WARN deprecated vue-cli@2.9.6: This package has been deprecated in favour of @vue/cli added 245 packages, and audited 246 packages in 18s 10 packages are looking for funding run `npm fund` for details 4 moderate severity vulnerabilities To address all issues (including breaking changes), run: npm audit fix --force Run `npm audit` for details. npm notice npm notice New minor version of npm available! 8.3.1 -> 8.4.1 npm notice Changelog: https://github.com/npm/cli/releases/tag/v8.4.1 npm notice Run npm install -g npm@8.4.1 to update! npm notice
インストールできたか、確認します。
% vue --version 2.9.6
大丈夫みたいです。
vueプロジェクトを作成しましょう。
Spring boot プロジェクトのディレクトリ直下に vjs という名前でプロジェクトを作ります。
% vue create vjs vue create is a Vue CLI 3 only command and you are using Vue CLI 2.9.6. You may want to run the following to upgrade to Vue CLI 3: npm uninstall -g vue-cli npm install -g @vue/cli
なんか、怒られた感じです。
提示されたふたつのコマンドを実行します。
% npm uninstall -g vue-cli removed 245 packages, and audited 1 package in 2s found 0 vulnerabilities % npm install -g @vue/cli npm WARN deprecated har-validator@5.1.5: this library is no longer supported npm WARN deprecated uuid@3.4.0: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details. npm WARN deprecated uuid@3.4.0: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details. npm WARN deprecated urix@0.1.0: Please see https://github.com/lydell/urix#deprecated npm WARN deprecated source-map-url@0.4.1: See https://github.com/lydell/source-map-url#deprecated npm WARN deprecated request@2.88.2: request has been deprecated, see https://github.com/request/request/issues/3142 npm WARN deprecated resolve-url@0.2.1: https://github.com/lydell/resolve-url#deprecated npm WARN deprecated source-map-resolve@0.5.3: See https://github.com/lydell/source-map-resolve#deprecated npm WARN deprecated apollo-tracing@0.15.0: The `apollo-tracing` package is no longer part of Apollo Server 3. See https://www.apollographql.com/docs/apollo-server/migration/#tracing for details npm WARN deprecated graphql-extensions@0.15.0: The `graphql-extensions` API has been removed from Apollo Server 3. Use the plugin API instead: https://www.apollographql.com/docs/apollo-server/integrations/plugins/ npm WARN deprecated apollo-cache-control@0.14.0: The functionality provided by the `apollo-cache-control` package is built in to `apollo-server-core` starting with Apollo Server 3. See https://www.apollographql.com/docs/apollo-server/migration/#cachecontrol for details. npm WARN deprecated @hapi/topo@3.1.6: This version has been deprecated and is no longer supported or maintained npm WARN deprecated @hapi/hoek@8.5.1: This version has been deprecated and is no longer supported or maintained npm WARN deprecated @hapi/bourne@1.3.2: This version has been deprecated and is no longer supported or maintained npm WARN deprecated @hapi/address@2.1.4: Moved to 'npm install @sideway/address' npm WARN deprecated @hapi/joi@15.1.1: Switch to 'npm install joi' npm WARN deprecated graphql-tools@4.0.8: This package has been deprecated and now it only exports makeExecutableSchema.\nAnd it will no longer receive updates.\nWe recommend you to migrate to scoped packages such as @graphql-tools/schema, @graphql-tools/utils and etc.\nCheck out https://www.graphql-tools.com to learn what package you should use instead added 945 packages, and audited 946 packages in 1m 68 packages are looking for funding run `npm fund` for details 13 vulnerabilities (6 moderate, 7 high) Some issues need review, and may require choosing a different dependency. Run `npm audit` for details.
気を取り直してもう一度createします。
% vue create vjs Vue CLI v4.5.15 ? Please pick a preset: (Use arrow keys) ❯ Default ([Vue 2] babel, eslint) Default (Vue 3) ([Vue 3] babel, eslint) Manually select features
何か選択しろって言われてますので、とりあえず、そのままでEnterしておきます。
Vue CLI v4.5.15 ? Please pick a preset: Default ([Vue 2] babel, eslint) Vue CLI v4.5.15 ✨ Creating project in /Users/satoshis/Documents/workspace/bookshelf/vjs. 🗃 Initializing git repository... ⚙️ Installing CLI plugins. This might take a while... added 1285 packages, and audited 1286 packages in 1m 86 packages are looking for funding run `npm fund` for details 70 vulnerabilities (2 low, 57 moderate, 11 high) To address issues that do not require attention, run: npm audit fix To address all issues (including breaking changes), run: npm audit fix --force Run `npm audit` for details. 🚀 Invoking generators... 📦 Installing additional dependencies... added 55 packages, and audited 1341 packages in 5s 91 packages are looking for funding run `npm fund` for details 72 vulnerabilities (2 low, 59 moderate, 11 high) To address issues that do not require attention, run: npm audit fix To address all issues (including breaking changes), run: npm audit fix --force Run `npm audit` for details. ⚓ Running completion hooks... 📄 Generating README.md... 🎉 Successfully created project vjs. 👉 Get started with the following commands: $ cd vjs $ npm run serve
Eclipseでプロジェクトをリフレッシュすると、以下のような構成で作られています。

Spring boot 上で動かしてみる
Spring boot 上で Vue を動かすには、vue をビルドしないといけないらしいです。
ただし、ビルドした JavaScript ファイルを Spring boot で動かすためには、src/main/resources/static に配置する必要があります。
package.json を変更します。
[json]
{
"name": "web",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build --dest ../src/main/resources/static/",
"lint": "vue-cli-service lint"
},
[/json]
ビルドしてみます。
% npm run build > vjs@0.1.0 build /Users/satoshis/Documents/workspace/bookshelf/vjs > vue-cli-service build --dest ../src/main/resources/static/ ⠧ Building for production... WARNING Compiled with 17 warnings 19:40:46 warning in ../node_modules/vue-router/dist/vue-router.esm-bundler.js "export 'computed' was not found in 'vue' warning in ./src/router.js "export 'default' (imported as 'Router') was not found in 'vue-router' warning in ../node_modules/vue-router/dist/vue-router.esm-bundler.js "export 'defineComponent' was not found in 'vue' warning in ../node_modules/vue-router/dist/vue-router.esm-bundler.js "export 'getCurrentInstance' was not found in 'vue' warning in ../node_modules/vue-router/dist/vue-router.esm-bundler.js "export 'h' was not found in 'vue' warning in ../node_modules/vue-router/dist/vue-router.esm-bundler.js "export 'inject' was not found in 'vue' warning in ../node_modules/vue-router/dist/vue-router.esm-bundler.js "export 'nextTick' was not found in 'vue' warning in ../node_modules/vue-router/dist/vue-router.esm-bundler.js "export 'onActivated' was not found in 'vue' warning in ../node_modules/vue-router/dist/vue-router.esm-bundler.js "export 'onDeactivated' was not found in 'vue' warning in ../node_modules/vue-router/dist/vue-router.esm-bundler.js "export 'onUnmounted' was not found in 'vue' warning in ../node_modules/vue-router/dist/vue-router.esm-bundler.js "export 'provide' was not found in 'vue' warning in ../node_modules/vue-router/dist/vue-router.esm-bundler.js "export 'reactive' was not found in 'vue' warning in ../node_modules/vue-router/dist/vue-router.esm-bundler.js "export 'ref' was not found in 'vue' warning in ../node_modules/vue-router/dist/vue-router.esm-bundler.js "export 'shallowRef' was not found in 'vue' warning in ../node_modules/vue-router/dist/vue-router.esm-bundler.js "export 'unref' was not found in 'vue' warning in ../node_modules/vue-router/dist/vue-router.esm-bundler.js "export 'watch' was not found in 'vue' warning in ../node_modules/vue-router/dist/vue-router.esm-bundler.js "export 'watchEffect' was not found in 'vue' File Size Gzipped ../src/main/resources/static/js/chunk-vendors.0a90606c.js 100.24 KiB 35.94 KiB ../src/main/resources/static/js/chunk-a020b95a.5f445348.js 23.58 KiB 8.18 KiB ../src/main/resources/static/js/app.3b3d464e.js 3.62 KiB 1.73 KiB ../src/main/resources/static/js/chunk-2d21767c.356128dc.js 2.12 KiB 0.82 KiB ../src/main/resources/static/js/chunk-2d0ba6fb.906a32b7.js 1.89 KiB 0.85 KiB ../src/main/resources/static/js/chunk-2d0bd7a8.7172d8bc.js 1.68 KiB 0.73 KiB ../src/main/resources/static/css/app.d40fc157.css 0.16 KiB 0.14 KiB Images and other types of assets omitted. DONE Build complete. The ../src/main/resources/static directory is ready to be deployed. INFO Check out deployment instructions at https://cli.vuejs.org/guide/deployment.html
なんか、いっぱい Warning が出てしまいました。
ぐぐってみると、node.js のバージョンがよろしくないみたいです。
node.js を latest ではなく stable に変えてみます。
% nodebrew install-binary stable Fetching: https://nodejs.org/dist/v16.13.2/node-v16.13.2-darwin-x64.tar.gz ############################################################################################################################## 100.0% Installed successfully % nodebrew ls v16.13.2 v17.4.0 current: v17.4.0 % nodebrew use 16.13.2 use v16.13.2
ビルドしなおしてみます。
% npm run build > web@0.1.0 build > vue-cli-service build ⠙ Building for production... DONE Compiled successfully in 2214ms 23:51:49 File Size Gzipped dist/js/chunk-vendors.e4229be5.js 95.12 KiB 34.05 KiB dist/js/app.6a32d4e9.js 4.58 KiB 1.63 KiB dist/css/app.fb0c6e1c.css 0.33 KiB 0.23 KiB Images and other types of assets omitted. DONE Build complete. The dist directory is ready to be deployed. INFO Check out deployment instructions at https://cli.vuejs.org/guide/deployment.html
今度は、Warningなど出ないで、正常にビルドできたようです。
Spring boot を起動してアクセスしてみます。
動きました!

動いたところで、次は参考サイトに合わせた感じでカスタマイズしていきましょう。
コンポーネントを作成する
参考サイトでは、唐突に、次のように書かれてます。
After the process is done. We create new folders and files like the following tree:
いきなり、AddTutorial.vue・Tutorial.vue・TutorialList.vue・TutorialDataService.js の4つのファイルが出現します。
しかし、ファイルの内容についてはまったく触れられてないのです。
ぐぐってみたら、Githubにそれっぽいソースコードが出てましたので、そちらを参考にコードを書いていきます。
≫ Github - danielcgithub/vue-getting-started
AddBook.vue
<template>
<div class="submit-form">
<div v-if="!submitted">
<div class="form-group">
<label for="title">Title</label>
<input type="text" name="title" class="form-control"
id="title" required v-model="book.title"
/>
</div>
<div class="form-group">
<label for="author">Author</label>
<input type="text" name="author" class="form-control"
id="author" required v-model="book.author"
/>
</div>
<button @click="saveBook" class="btn btn-success">Submit</button>
</div>
<div v-else>
<p>Submit successed!</p>
<button class="btn btn-success" @click="newBook">Add</button>
</div>
</div>
</template>
<script>
import BookDataService from "../services/BookDataService";
export default {
name: "add-book",
data() {
return {
book: {
id: null,
title: "",
author: ""
},
submitted: false
};
},
methods: {
saveBook() {
var data = {
title: this.book.title,
author: this.book.author
};
BookDataService.create(data)
.then(response => {
this.book.id = response.data.id;
})
.catch(e => {
console.log(e);
});
},
newBook() {
this.submitted = false;
this.book = {}
}
}
};
</script>
Book.vue
<template>
<div v-if="currentBook" class="edit-form">
<h4>Book</h4>
<form>
<div class="form-group">
<label for="title">Title</label>
<input type="text" class="form-control" id="title" v-model="currentBook.title"/>
</div>
<div class="form-group">
<label for="author">Author</label>
<input type="text" class="form-control" id="author" v-model="currentBook.author" />
</div>
</form>
<button class="badge badge-primary mr-2" @click="deleteBook">Delete</button>
<button type="submit" class="badge badge-success" @click="updateBook">Update</button>
<p>{{ message }}</p>
</div>
<div v-else>
<br />
<p>Click Book</p>
</div>
</template>
<script>
import BookDataService from "../services/BookDataService";
export default {
name: "book",
data() {
return {
currentBook: null,
message: ''
};
},
methods: {
getBook(id) {
BookDataService.get(id)
.then(response => {
this.currentBook = response.data;
console.log(response.data);
})
.catch(e => {
console.log(e);
});
},
updateBook() {
BookDataService.update(this.currentBook.id, this.currentBook)
.then(response => {
console.log(response.data);
this.message = 'The book updated.';
})
.catch(e => {
console.log(e);
});
},
deleteBook() {
BookDataService.delete(this.currentBook.id)
.then(response => {
console.log(response.data);
this.$router.push({ name: "books" });
})
.catch(e => {
console.log(e);
});
}
},
mounted() {
this.message = '';
this.getBook(this.$route.params.id);
}
};
</script>
BookList.vue
<template>
<div class="list row">
<div class="col-md-8">
<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 class="col-md-6">
<h4>Book List</h4>
<ul class="list-group">
<li class="list-group-item" :class="{ active: index == currentIndex }"
v-for="(book, index) in books" :key="index"
@click="setActiveBook(book, index)">{{ book.title }}
</li>
</ul>
</div>
<div class="col-md-6">
<div v-if="currentBook">
<h4>Book</h4>
<div>{{ currentBook.title }}</div>
<div>{{ currentBook.author }}</div>
<a class="badge badge-warning" :href="'/books' + currentBook.id">Edit</a>
</div>
</div>
</div>
</template>
<script>
import BookDataService from "../services/BookDataService";
export default {
name: "books-list",
data() {
return {
books: [],
currentBook: null,
currentIndex: -1,
title: ""
};
},
methods: {
retrieveBooks() {
BookDataService.getAll()
.then(response => {
this.books = response.data;
console.log(response.data);
})
.catch(e => {
console.log(e);
});
},
setActiveBook(book, index) {
this.currentBook = book;
this.currentIndex = index;
},
searchTitle() {
BookDataService.findByTitle(this.title)
.then(response => {
this.books = response.data;
console.log(response.data);
})
.catch(e => {
console.log(e);
});
}
},
mounted() {
this.retrieveBooks();
}
}
</script>
BookDataService.js
import http from "../http-common";
class BookDataService {
getAll() {
return http.get("/books");
}
get(id) {
return http.get(`/books/${id}`);
}
create(data) {
return http.post("/books", data);
}
update(id, data) {
return http.put(`/books/${id}`, data);
}
delete(id) {
return http.delete(`/books/${id}`);
}
findByTitle(title) {
return http.get(`/books?title=${title}`);
}
}
export default new BookDataService();
Vue Router をインストールする
とりあえず、Vue Router をインストールしないといけないみたいです。
詳しくは調べてないんですが、そうらしいです。
まあ、とりあえずは動かしてみることに専念して、あとで調べてみましょう。
公式ドキュメントには、SPAを構築するためと書かれてますので、SPAでなければ不要なのかも?
よくわからないですが、参考サイトでも使われているので、あまり考えずに使うことにします。
% npm install vue-router added 23 packages, and audited 24 packages in 6s 1 package is looking for funding run `npm fund` for details found 0 vulnerabilities
router.jsを作成する
vjs/src に、router.js を作ればいいようです。
import Vue from "vue";
import Router from "vue-router";
Vue.use(Router);
export default new Router({
mode: "history",
routes: [
{
path: "/",
alias: "/books",
name: "books",
component: () => import("./components/BooksList")
},
{
path: "/books/:id",
name: "book-details",
component: () => import("./components/Book")
},
{
path: "/add",
name: "add",
component: () => import("./components/AddBook")
}
]
});
main.js を変更する
main.js に作成した 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')
App.vue を変更する
参考サイトではナビゲーションなどを追加してますが、ここでは省略します。
HelloWorldコンポーネントやstyleは、とりあえず残したままにしてみます。
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
<div class="container mt-3">
<router-view />
</div>
</div>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue'
export default {
name: 'App',
components: {
HelloWorld
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
axiosをインストールする
axiosは、HTTPクライアントだそうです。
Promise based HTTP client for the browser and node.js
% npm install axios up to date, audited 1342 packages in 21s 91 packages are looking for funding run `npm fund` for details 73 vulnerabilities (2 low, 60 moderate, 11 high) To address issues that do not require attention, run: npm audit fix To address all issues (including breaking changes), run: npm audit fix --force Run `npm audit` for details.
73 vulnerabilities (2 low, 60 moderate, 11 high)
えええ?
大丈夫なの?
まあ、テスト的に作って見るだけなので、気にしないことにしましょう。
http-common.jsを作成する
srcの下に、http-common.js を作成します。
import axios from "axios";
export default axios.create({
baseURL: "http://localhost:8080/api",
headers: {
"Content-type": "application/json"
}
});
ここまで作ればよさそうな雰囲気なので、ビルドしなおしてみます。
% npm run build > web@0.1.0 build > vue-cli-service build --dest ../src/main/resources/static/ ⠦ Building for production... DONE Compiled successfully in 5084ms 16:41:44 File Size Gzipped ../src/main/resources/static/js/chunk-vendors.2909625e.js 121.46 KiB 43.05 KiB ../src/main/resources/static/js/chunk-a020b95a.5f445348.js 23.58 KiB 8.18 KiB ../src/main/resources/static/js/app.9e87135a.js 6.06 KiB 2.28 KiB ../src/main/resources/static/js/chunk-2d21767c.6e9e2cc9.js 2.12 KiB 0.82 KiB ../src/main/resources/static/js/chunk-2d0ba6fb.8c20d7cb.js 1.89 KiB 0.85 KiB ../src/main/resources/static/js/chunk-2d0bd7a8.2c780f7e.js 1.68 KiB 0.73 KiB ../src/main/resources/static/css/app.fb0c6e1c.css 0.33 KiB 0.23 KiB Images and other types of assets omitted. DONE Build complete. The ../src/main/resources/static directory is ready to be deployed. INFO Check out deployment instructions at https://cli.vuejs.org/guide/deployment.html
成功しました!
Spring bootを起動してアクセスしてみます。

なんか出ました。
でも、追加するためのリンクが存在しないので、どうすることもできません。
HelloWorldコンポーネントとロゴの画像はなくてもいいので、まずこれを消してみます。
<template>
<div id="app">
<div class="container mt-3">
<router-view />
</div>
</div>
</template>
<script>
export default {
name: 'App',
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>

参考サイトではナビゲーションにリンクがありましたが、ここではシンプルにリンクを追加してみます。
<template>
<div id="app">
<div>
<ul>
<li><a href="/">HOME</a></li>
<li><a href="/books">Books</a></li>
<li><a href="/add">Add Book</a></li>
</ul>
</div>
<div class="container mt-3">
<router-view />
</div>
</div>
</template>
<script>
export default {
name: 'App',
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
ul {
list-style-type: none;
}
</style>

Books を /books に、Add Book を /add にリンクしているのですが、Spring bootがアクセスを拾って Whitelabel Error Page に飛ばされてしまいます。
≫ Github - danielcgithub/vue-getting-started
こちらを見ても、Vue は npm run serve で動かして、Spring bootは別で動かしているので、Spring boot 内に配置した Vue を動かすことは考えてないみたいです。
Spring boot の Controller にも、@CrossOrigin アノテーションがつけられているので、別ホストで動かす前提のようです。
ぐぐってみたところ、Spring boot 内にまとめるのは簡単にはできなさそうです。
Vue のディレクトリで、npm run serve で Vue を起動すれば、とりあえずは動きます。
% npm run serve > web@0.1.0 serve > vue-cli-service serve INFO Starting development server... 98% after emitting CopyPlugin DONE Compiled successfully in 2632ms 18:23:17 App running at: - Local: http://localhost:8081/ - Network: http://192.168.1.13:8081/ Note that the development build is not optimized. To create a production build, run npm run build.
本を追加してみます。

Submit すると、成功したようです。

HOME または Books のリンクをクリックすると追加されていることがわかります。

もうひとつ追加してみます。

本のタイトルをクリックすると、リストの下に詳細が表示されます。

Editをクリックすると、入力フォームに切り替わって、編集または、Deleteボタンで削除ができるようになってます。

試してみたところ、編集も削除も問題なく動作しました。