はじめに
本記事では、JavaとSpring Bootを使用して、シンプルなTODOアプリのREST APIを作成する方法を紹介します。タスクの作成、更新、削除、取得ができるAPIを実装します。このAPIを作成することで、Spring Bootの基本的な使い方を学び、実際のプロジェクトでよく使われるRESTful APIの設計についても理解を深めることができます。
使用する技術
- Java 21
- Spring Boot 3.4.2
- Gradle
- H2データベース(軽量で開発用のインメモリデータベース)
- Lombok
プロジェクトのセットアップ
1. Spring Initializrを使ったプロジェクトの作成
Spring Initializrを使って、基本的なSpring Bootプロジェクトを作成します。
-
Spring Initializrのサイトにアクセス
https://start.spring.io/ にアクセスします。 -
プロジェクトメタデータを設定
- Project: Gradle Project
- Language: Java
- Spring Boot: 3.4.2
-
Project Metadata:
- Group: com.example
- Artifact: todo-api
- Name: todo-api
- Description: A simple Todo API
- Package name: com.example.todo
- Packaging: Jar
- Java: 21
-
依存関係の選択
以下の依存関係を追加します。- Spring Web (REST API作成に必要)
- Spring Data JPA (JPAを使ってデータベース操作)
- H2 Database (開発用のインメモリデータベース)
- Lombok (ボイラープレートコードを減らすため)
-
プロジェクトの生成
上記の設定を終えたら、「Generate」ボタンをクリックしてプロジェクトをダウンロードします。 -
ダウンロードしたファイルを解凍して、IDE(IntelliJやEclipseなど)で開きます。
2. プロジェクトの依存関係設定(build.gradle
)
ダウンロードしたプロジェクトには、build.gradle
ファイルが自動で生成されています。内容は以下のようになっていますが、念のため確認してください。
build.gradle
plugins {
id 'org.springframework.boot' version '3.4.2'
id 'io.spring.dependency-management' version '1.1.0'
id 'java'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '21'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'com.h2database:h2'
implementation 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
test {
useJUnitPlatform()
}
3. application.properties
の設定
次に、src/main/resources/application.properties
に、H2データベースの設定を追加します。
src/main/resources/application.properties
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
4. エンティティの作成
次に、TODOタスクを表すTodo
エンティティクラスを作成します。
package com.example.todo.entity;
import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Data
@Entity
public class Todo {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private String description;
private boolean completed;
}
5. リポジトリの作成
Todo
エンティティを操作するためのリポジトリを作成します。
package com.example.todo.repository;
import com.example.todo.entity.Todo;
import org.springframework.data.jpa.repository.JpaRepository;
public interface TodoRepository extends JpaRepository<Todo, Long> {
}
6. サービス層の作成
サービス層は、ビジネスロジックを担当します。TODOタスクの作成、更新、削除、取得を担当するメソッドを実装します。
package com.example.todo.service;
import com.example.todo.entity.Todo;
import com.example.todo.repository.TodoRepository;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
@Service
public class TodoService {
private final TodoRepository todoRepository;
public TodoService(TodoRepository todoRepository) {
this.todoRepository = todoRepository;
}
public List<Todo> getAllTodos() {
return todoRepository.findAll();
}
public Optional<Todo> getTodoById(Long id) {
return todoRepository.findById(id);
}
public Todo createTodo(Todo todo) {
return todoRepository.save(todo);
}
public Todo updateTodo(Long id, Todo todo) {
if (!todoRepository.existsById(id)) {
return null;
}
todo.setId(id);
return todoRepository.save(todo);
}
public boolean deleteTodo(Long id) {
if (todoRepository.existsById(id)) {
todoRepository.deleteById(id);
return true;
}
return false;
}
}
7. コントローラーの作成
次に、REST APIを提供するコントローラーを作成します。
package com.example.todo.controller;
import com.example.todo.entity.Todo;
import com.example.todo.service.TodoService;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Optional;
@RestController
@RequestMapping("/api/todos")
public class TodoController {
private final TodoService todoService;
public TodoController(TodoService todoService) {
this.todoService = todoService;
}
@GetMapping
public List<Todo> getAllTodos() {
return todoService.getAllTodos();
}
@GetMapping("/{id}")
public Optional<Todo> getTodoById(@PathVariable Long id) {
return todoService.getTodoById(id);
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public Todo createTodo(@RequestBody Todo todo) {
return todoService.createTodo(todo);
}
@PutMapping("/{id}")
public Todo updateTodo(@PathVariable Long id, @RequestBody Todo todo) {
return todoService.updateTodo(id, todo);
}
@DeleteMapping("/{id}")
public void deleteTodo(@PathVariable Long id) {
if (!todoService.deleteTodo(id)) {
throw new RuntimeException("Todo not found");
}
}
}
8. 動作確認
Spring Bootアプリケーションが正常に動作することを確認するために、以下の手順で確認します。
8.1 H2コンソールの使用
Spring BootはH2データベースをインメモリで使用しており、H2コンソールを利用してデータベースの内容を確認できます。
-
application.properties
ファイルに以下の設定があることを確認して、H2コンソールを有効にしていることを確認します。spring.h2.console.enabled=true spring.h2.console.path=/h2-console
-
アプリケーションを起動します。
-
ブラウザで
http://localhost:8080/h2-console
にアクセスします。 -
データベースのURLを確認します。
jdbc:h2:mem:testdb
と設定されていれば、そのままでOKです。usernameとpasswordは、application.propertiesに書いている値を記載します。 -
「Connect」ボタンをクリックして、H2コンソールに接続します。これで、TODOテーブルの内容が確認できるようになります。
8.2 PostmanでAPIを確認
次に、Postmanやcurlなどを使用して、REST APIの動作を確認します。
- GET /api/todos: すべてのTODOを取得
- GET /api/todos/{id}: 指定したIDのTODOを取得
- POST /api/todos: 新しいTODOを作成
- PUT /api/todos/{id}: 指定したIDのTODOを更新
- DELETE /api/todos/{id}: 指定したIDのTODOを削除
各エンドポイントにリクエストを送信して、正常に動作することを確認します。
9. テストの作成
アプリケーションの動作をテストするために、MockMvc
を使ってREST APIのテストを行います。MockMvc
は、Spring MVCのテスト用に提供されているツールで、実際にサーバーを立ち上げることなく、HTTPリクエストをシミュレーションできます。
9.1 テストクラスの作成
以下のコードで、TODOアプリケーションのREST APIに対するテストを作成します。
package com.example.todo;
import com.example.todo.entity.Todo;
import com.example.todo.repository.TodoRepository;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@SpringBootTest
@AutoConfigureMockMvc
public class TodoControllerTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private TodoRepository todoRepository;
@Autowired
private ObjectMapper objectMapper;
@BeforeEach
public void setUp() {
todoRepository.deleteAll();
}
@Test
public void testGetAllTodos() throws Exception {
Todo todo1 = new Todo();
todo1.setTitle("Test Todo 1");
todo1.setDescription("Description for test todo 1");
todo1.setCompleted(false);
todoRepository.save(todo1);
mockMvc.perform(get("/api/todos"))
.andExpect(status().isOk())
.andExpect(jsonPath("$[0].title").value("Test Todo 1"))
.andExpect(jsonPath("$[0].description").value("Description for test todo 1"));
}
@Test
public void testCreateTodo() throws Exception {
Todo todo = new Todo();
todo.setTitle("New Todo");
todo.setDescription("This is a new todo");
todo.setCompleted(false);
mockMvc.perform(post("/api/todos")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(todo)))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.title").value("New Todo"))
.andExpect(jsonPath("$.description").value("This is a new todo"));
// DBに新しいTODOが作成されたか確認
Todo createdTodo = todoRepository.findAll().get(0);
assertEquals("New Todo", createdTodo.getTitle());
assertEquals("This is a new todo", createdTodo.getDescription());
}
@Test
public void testUpdateTodo() throws Exception {
Todo todo = new Todo();
todo.setTitle("Todo to update");
todo.setDescription("Description of todo");
todo.setCompleted(false);
Todo savedTodo = todoRepository.save(todo);
savedTodo.setTitle("Updated Todo");
savedTodo.setDescription("Updated description");
mockMvc.perform(put("/api/todos/" + savedTodo.getId())
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(savedTodo)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.title").value("Updated Todo"))
.andExpect(jsonPath("$.description").value("Updated description"));
// DBの値が更新されたことを確認
Todo updatedTodo = todoRepository.findById(savedTodo.getId()).get();
assertEquals("Updated Todo", updatedTodo.getTitle());
assertEquals("Updated description", updatedTodo.getDescription());
}
@Test
public void testDeleteTodo() throws Exception {
Todo todo = new Todo();
todo.setTitle("Todo to delete");
todo.setDescription("Description of todo to delete");
todo.setCompleted(false);
Todo savedTodo = todoRepository.save(todo);
mockMvc.perform(delete("/api/todos/" + savedTodo.getId()))
.andExpect(status().isOk());
// DBから削除されていることを確認
assertTrue(todoRepository.findById(savedTodo.getId()).isEmpty());
}
}
9.2 テストの説明
-
testGetAllTodos:
GET /api/todos
エンドポイントをテストします。保存したTODOが返されることを確認します。 -
testCreateTodo:
POST /api/todos
エンドポイントをテストします。新しいTODOを作成し、レスポンスが正しいことを確認した後、DBに新しいTODOが保存されたことも検証します。 -
testUpdateTodo:
PUT /api/todos/{id}
エンドポイントをテストします。既存のTODOを更新し、レスポンスに反映されることを確認した後、DBに保存されたTODOの内容が更新されていることも検証します。 -
testDeleteTodo:
DELETE /api/todos/{id}
エンドポイントをテストします。指定したIDのTODOを削除し、そのTODOがDBから削除されたことを確認します。
9.3 テスト実行方法
依存関係にJUnitを使用している場合、IDEで直接テストを実行するか、以下のコマンドでテストを実行できます。
./gradlew test
結論
この記事では、Java 21とSpring Boot 3.4.2を使用して、シンプルなTODOアプリのREST APIを実装しました。これで、Spring Bootを使ったRESTful APIの基本的な実装方法を理解できたと思います。