Vite(Vue.js)+SpringBoot2.6でのREST API連携
はじめに
以前作成したVite(Vue3.2)のToDoアプリが好評だったみたいなので、SpringBootで作成したREST APIと連携する際の実装についても、紹介してみたいと思います。
こうやって、記事をまとめていくと、componentsを事前に用意できれば、ローコード開発でしょ?って思うぐらいに、簡単になってきましたね。Vue3.3(2022.2Q)で登場するReactivity Transformも楽しみにしています。
SpringBootでREST API作成
ToDo用のREST APIを新たに作成します。Javaの開発環境構築は、こちらを参考にしてください。ちなみにDBは、H2を利用しているので、Oracleのセットアップ不要です。Oracleとの連携方法もコメントで追記しています。
プロジェクト作成
F1で「[F1]を押して、「Spring Initializr: Create a Gradle Project」でプロジェクトを作成する。以下の項目以外は、デフォルトのままにします。
・Artifact Id ... todoapp
・Java version ... 17
・dependencies ... 手動で追加
・出力フォルダ ... workspace
REST API作成
ここからは、ひたすら実装していきます。Spring Bootは、特に新しい要素もないので説明は割愛します。
...
dependencies {
// Spring Web
implementation 'org.springframework.boot:spring-boot-starter-web'
// Spring Boot DevTools
developmentOnly 'org.springframework.boot:spring-boot-devtools'
// Spring Data JDBC
implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-validation'
// H2 Database
runtimeOnly 'com.h2database:h2'
// Oracle
//implementation fileTree(dir: 'lib/ojdbc8.jar', include: ['*.jar'])
// Lombok
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
// Swagger
implementation 'org.springdoc:springdoc-openapi-ui:1.6.6'
implementation 'org.springdoc:springdoc-openapi-data-rest:1.6.6'
// Test
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
...
spring:
sql:
init:
encoding: UTF-8
mode: always
h2:
console:
enabled: true
datasource:
driver-class-name: org.h2.Driver
# Oracleの場合、oracle.jdbc.OracleDriver
url: jdbc:h2:mem:test
# Oracleの場合、jdbc:oracle:thin:@//database:1521/XEPDB1
username: sa
password:
package com.example.todoapp.entity;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import org.springframework.data.annotation.Id;
import lombok.Data;
@Data
public class Todo {
@Id
private Long id;
@NotBlank
@Size(min = 1, max = 20)
private String title;
@NotNull
private Boolean done;
}
package com.example.todoapp.controller;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import com.example.todoapp.entity.Todo;
import com.example.todoapp.repository.TodoRepository;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import lombok.RequiredArgsConstructor;
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/todo")
public class TodoController {
private final TodoRepository todoRepository;
@GetMapping
List<Todo> getAll() {
return todoRepository.findAll();
}
@GetMapping("{id}")
Todo get(@PathVariable("id") Long id) {
return todoRepository.findById(id).get();
}
@PostMapping
Todo insert(@Validated @RequestBody Todo todo) {
return todoRepository.save(todo);
}
@DeleteMapping("{id}")
void delete(@PathVariable("id") Long id) {
todoRepository.deleteById(id);
}
}
package com.example.todoapp.repository;
import java.util.List;
import org.springframework.data.repository.CrudRepository;
import com.example.todoapp.entity.Todo;
public interface TodoRepository extends CrudRepository<Todo, Long> {
/** 全件取得 */
List<Todo> findAll();
}
package com.example.todoapp.config;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.ErrorPage;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
@Component
public class ServletCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
@Override
public void customize(TomcatServletWebServerFactory factory) {
// vue-routerをHistoryモードで動かすと、/以外が404となってしまうため、index.htmlに飛ばすように修正。
ErrorPage error404Page = new ErrorPage(HttpStatus.NOT_FOUND, "/index.html");
factory.addErrorPages(error404Page);
}
}
package com.example.todoapp.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
// viteとの開発用に、3000からのアクセスを許可する。
registry.addMapping("/api/**")
.allowedMethods("*")
.allowedOrigins("http://localhost:3000");
}
}
TRUNCATE TABLE TODO;
INSERT INTO TODO (TITLE, DONE) VALUES ('起きる', 0);
INSERT INTO TODO (TITLE, DONE) VALUES ('歯を磨く', 1);
-- Oracleの場合、最初にテーブルを手動追加してから、コメントを外す。
-- DROP TABLE TODO CASCADE CONSTRAINTS;
-- テーブル作成
CREATE TABLE TODO
(
ID NUMBER(15) GENERATED ALWAYS AS IDENTITY,
TITLE VARCHAR2(100 Byte),
DONE BOOLEAN -- Oracleの場合、NUMBER(1)
);
ALTER TABLE TODO ADD CONSTRAINT PK_TODO PRIMARY KEY (ID);
デバック方法
サイドバーにある「実行とデバック」から同ボタンを選択し、Javaを選択すると、プログラムが実行されます。その後、構成を保存する場合は、プログラムが実行している状態で、設定ボタンで、再度Javaを選択すると、launch.jsonが作成されます。
動作確認用のURLは以下の通り。新規登録する場合は、POSTの際にidを削除します。
- Swagger UI : http://localhost:8080/swagger-ui.html
- H2DB : http://localhost:8080/h2-console (JDBC URL=jdbc:h2:mem:test)
Vite(Vue.js)版のToDoアプリ修正
前回作成したToDoアプリに対して、Vite側にaxiosをinstallして、REST API版に変更していきます。
> npm i axios
REST APIとの連携
REST APIとの連携は、新たにserviceクラスを作成して、その中で管理するようにしていきます。
その際、REST APIでToDoを新規登録するときは、idをnullにしたいので、idの後に?をつけていきます。ロジックの部分が、javaっぽくなってきましたね。
import axios from "axios";
import { reactive } from "vue";
import { Task } from "../models/Task";
class TodoService {
// RESTAPI URL
private RESTAPI_URL = "/api/todo/";
// タスクリスト
private tasks: Task[] = reactive([]);
// タスクを取得する。
get todoTasks(): Task[] {
return this.tasks;
}
// 全タスクを取得する。
public getAllTasks(): void {
axios.get<Task[]>(this.RESTAPI_URL).then((res) => {
Array.prototype.push.apply(this.tasks, res.data);
});
}
// タスクを追加する。
public addTask(newTaskTitle: string): void {
const newTask: Task = {
title: newTaskTitle,
done: false,
};
axios.post<Task>(this.RESTAPI_URL, newTask).then((res) => {
this.tasks.push(res.data);
});
}
// タスクを削除する。
public deleteTask(id?: number): void {
const index = this.tasks.findIndex((t) => t.id === id);
if (index !== undefined) {
this.tasks.splice(index, 1);
axios.delete(this.RESTAPI_URL + id);
}
}
// タスクのDoneを変更する。
public doneTask(id?: number): void {
const task = this.tasks.find((t) => t.id === id);
if (task !== undefined) {
task.done = !task.done;
axios.post<Task>(this.RESTAPI_URL, task);
}
}
}
export default new TodoService();
<script setup lang="ts">
import todoService from "../services/TodoService";
import TaskAdd from "../components/TaskAdd.vue";
import TaskList from "../components/TaskList.vue";
// taskをすべて取得する。
todoService.getAllTasks();
</script>
<template>
<h1 class="mt-4">Todo List</h1>
<div class="row">
<div class="col-xl-6 col-md-6">
<TaskAdd @add="(newTaskTitle) => todoService.addTask(newTaskTitle)"></TaskAdd>
<TaskList :tasks="todoService.todoTasks" @delete="(id) => todoService.deleteTask(id)" @done="(id) => todoService.doneTask(id)"></TaskList>
</div>
</div>
</template>
// idに?を追加
export interface Task {
id?: number;
title: string;
done: boolean;
}
// idに?を追加
const emit = defineEmits<{
(eventName: "done", id?: number): void;
(eventName: "delete", id?: number): void;
}>();
デバック方法
REST APIの連携する際、データの中身を見たいときもあると思うんで、デバック方法についても紹介します。以前は「Debugger for Chrome」という拡張機能を入れないとデバックできなかったのですが、2021/7/16以降は、VSCodeに標準装備されたため不要になりました。関連記事
- サイドバーにある「実行とデバック」から同ボタンを選択し、Chromeを選択する。※launch.jsonのファイルが残っている場合は、ファイルを削除しないとプルダウンが選べません。あと、EdgeはLOCALでしか選択できないようです。
- その時に作成されるlaunch.jsonのポート番号を3000に変更する。
- 「実行とデバック」で、「Run Script:dev」を実行し、その後「Launch Chrome against localhost」を実行する。
※Run Script:devが表示されない場合は、Node.js...からdevを選択する。 - 好きなソースの位置にブレークポイントを設定してみて、デバックできるか確認する。ちなみに、デバック中はブレイクポイントは設定できません。
Spring Bootとの連携
Spring Bootと連携する際は、以下の修正を行います。修正後は、「実行とデバック」でNode.js...からbuildを実行すると、jsファイルが生成されますので、Javaで動作確認(http://localhost:8080)してみてください。
- javaのフォルダ配下に、webフォルダ(どこでもよい)を作成し、ソースを移動する。
- /apiに対するproxy設定(8080に変更)を追加
- build時の出力先を、Java側のsrc/main/resources/staticに変更
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
build: {
outDir: "../src/main/resources/static",
},
server: {
proxy: {
"/api": "http://localhost:8080",
},
},
});
おわりに
今回は、いつも仕事で使っているSpring BootでRESTサービスを構築しました。他にもDenoやnode.jsでREST APIを構築すると、Typescriptのみで開発できるので、楽かもしれません。
引き続き、Vite関連の記事は掲載していこうと思いますので、よろしくお願いいたします。
Discussion