SpringBootでREST APIを実装してみた

今回はSpringBootでREST APIを実装してみます。

## 動作環境

MySQL：8.0
SpringBoot：3.1.4
Java：17

## APIのインターフェース

今回実装するAPIのインターフェースは下記になります。

| endpoint                 | method | note |
|:-------------------------|:-------|:-------------|
| {host}/api/users | POST   | ユーザーの作成|
| {host}/api/users/ | GET    | ユーザー全件取得             |
| {host}/api/users/:id | GET    | 指定したIDのユーザーを取得             |
| {host}/api/users/:id | PUT    | 指定したIDのユーザー情報を変更         |
| {host}/api/users/:id | DELETE | 指定したIDのユーザーを削除         |

## データベースの準備

今回はMySQLを使います。
データベースとテーブルを下記のように作成しておいてください。

```sql
CREATE DATABASE sample_api;
USE sample_api;
```

```sql
CREATE TABLE users (
    id    BIGINT(20) AUTO_INCREMENT PRIMARY KEY,
    name  VARCHAR(1024) NOT NULL,
    email VARCHAR(1024) NOT NULL
);
```

## APIの実装

### Spring Initializrでプロジェクト作成

まずはSpring Initializrでプロジェクトを作成します。（Intellijとかお持ちの方は同じような内容でNew ProjectしていただいてもOKです）

https://start.spring.io/#!type=maven-project&language=java&platformVersion=3.1.4&packaging=jar&jvmVersion=17&groupId=com.example&artifactId=sample-api&name=sample-api&description=Sample%20RestAPI&packageName=com.example.sample-api&dependencies=web,lombok,data-jpa,mysql

リンクを開いていただいたら、今回扱う設定内容が反映されているので、そのままGENERATEを押してプロジェクトをダウンロードしてください。

任意のIDE等で開いていただいたら、下記のファイルだけ名称を変更します。（やらなくてもいいですが自分はyml形式が好きなので。。。）

```
src/main/resource/application.properties -> src/main/resource/application.yml
```


### データベースの接続情報設定
次にデータベースの接続情報を設定ファイルに追記します。
username, passwordは各自書き換えておいてください。

```yml:src/min/resource/application.yml
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/sample_api
    username: {user_name}
    password: {password}
    driverClassName: com.mysql.cj.jdbc.Driver
```

### SpringApplicationの起動

ここまでできたら一旦SpringApplicationを起動して、初期設定に問題がないか確認しておきましょう。
起動したら下記のようになっていれば大丈夫です。

```
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.1.4)

2023-09-29T16:35:00.542+09:00  INFO 77060 --- [           main] c.e.sampleapi.SampleApiApplication       : Starting SampleApiApplication using Java 17.0.6 with PID 77060 (/Users/.../Downloads/sample-api/target/classes started by yunosukenaito in /Users/yunosukenaito/Downloads/sample-api)
2023-09-29T16:35:00.543+09:00  INFO 77060 --- [           main] c.e.sampleapi.SampleApiApplication       : No active profile set, falling back to 1 default profile: "default"
2023-09-29T16:35:00.743+09:00  INFO 77060 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2023-09-29T16:35:00.749+09:00  INFO 77060 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 4 ms. Found 0 JPA repository interfaces.
2023-09-29T16:35:00.932+09:00  INFO 77060 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2023-09-29T16:35:00.936+09:00  INFO 77060 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2023-09-29T16:35:00.936+09:00  INFO 77060 --- [           main] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.13]
2023-09-29T16:35:00.970+09:00  INFO 77060 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2023-09-29T16:35:00.970+09:00  INFO 77060 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 408 ms
2023-09-29T16:35:01.015+09:00  INFO 77060 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2023-09-29T16:35:01.154+09:00  INFO 77060 --- [           main] com.zaxxer.hikari.pool.HikariPool        : HikariPool-1 - Added connection com.mysql.cj.jdbc.ConnectionImpl@1d7eb170
2023-09-29T16:35:01.155+09:00  INFO 77060 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2023-09-29T16:35:01.171+09:00  INFO 77060 --- [           main] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [name: default]
2023-09-29T16:35:01.198+09:00  INFO 77060 --- [           main] org.hibernate.Version                    : HHH000412: Hibernate ORM core version 6.2.9.Final
2023-09-29T16:35:01.199+09:00  INFO 77060 --- [           main] org.hibernate.cfg.Environment            : HHH000406: Using bytecode reflection optimizer
2023-09-29T16:35:01.255+09:00  INFO 77060 --- [           main] o.h.b.i.BytecodeProviderInitiator        : HHH000021: Bytecode provider name : bytebuddy
2023-09-29T16:35:01.306+09:00  INFO 77060 --- [           main] o.s.o.j.p.SpringPersistenceUnitInfo      : No LoadTimeWeaver setup: ignoring JPA class transformer
2023-09-29T16:35:01.376+09:00  INFO 77060 --- [           main] o.h.b.i.BytecodeProviderInitiator        : HHH000021: Bytecode provider name : bytebuddy
2023-09-29T16:35:01.463+09:00  INFO 77060 --- [           main] o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2023-09-29T16:35:01.465+09:00  INFO 77060 --- [           main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2023-09-29T16:35:01.479+09:00  WARN 77060 --- [           main] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2023-09-29T16:35:01.615+09:00  INFO 77060 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2023-09-29T16:35:01.619+09:00  INFO 77060 --- [           main] c.e.sampleapi.SampleApiApplication       : Started SampleApiApplication in 1.267 seconds (process running for 1.53)
```

### Entityの実装

まずはEntityを実装します。entityのディレクトリを作成してその下にUserクラスを作成します。

```java:src/main/java/com/example/sampleapi/entity/User.java
package com.example.sampleapi.entity;

import jakarta.persistence.*;
import lombok.Data;

@Data
@Entity
@Table(name = "users")
public class User {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "id")
  private Long id;

  @Column(name = "name")
  private String name;

  @Column(name = "email")
  private String email;

}

```

@Entityアノテーションを付与することで、このクラスがデータベースのEntityであることを示します。
@Tableアノテーションは、データベースのどのテーブルに対応するかを指定します。（先ほど作成したusersテーブルに対応）
@Idアノテーションでテーブルの主キーであることを明示し、@GenerateValueのIDENTITYを指定することで、データを追加時に自動的に主キーを生成することができます。
@Columnアノテーションは、テーブルのカラムとプロパティ名を紐づけます。


### Repositoryの実装

次はRepositoryを実装していきます。

```java:src/main/java/com/example/sampleapi/repository/UserRepository.java
package com.example.sampleapi.repository;

import com.example.sampleapi.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}
```

@Repositoryは、このインターフェースがリポジトリとして使用されることを示すために付与します。また、JpaRepositoryをextendsすることで、usersテーブルに対して、データの追加や更新・削除などのメソッドを使うことができます。
拡張をしたい場合は自身で定義したこのインターフェースのクラスにメソッドを追加していく形になります。
JpaRepositoryには対象のEntityクラスと主キーとなる型を指定します。

使えるメソッドは[公式ドキュメント（JpaRepository）](https://docs.spring.io/spring-data/jpa/docs/current/api/org/springframework/data/jpa/repository/JpaRepository.html)を参照してみてください。

### Controllerの実装

最後にControllerクラスを実装します。Controllerクラスは実際にリクエストを受け付ける部分になります。

```java:src/main/java/com/example/sampleapi/controller/UserController.java
package com.example.sampleapi.controller;

import com.example.sampleapi.entity.User;
import com.example.sampleapi.repository.UserRepository;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api/users")
public class UserController {

  private UserRepository userRepository;

  public UserController(UserRepository userRepository) {
    this.userRepository = userRepository;
  }

  @GetMapping
  public List<User> getAll() {
    return this.userRepository.findAll();
  }

  @GetMapping("{id}")
  public User getById(@PathVariable("id") Long id) {
    return this.userRepository.findById(id).orElseThrow();
  }

  @PostMapping
  public void save(@RequestBody User user) {
    this.userRepository.save(user);
  }

  @PutMapping("{id}")
  public void update(@PathVariable("id") Long id, @RequestBody User user) {
    user.setId(id);
    this.userRepository.save(user);
  }

  @DeleteMapping("{id}")
  public void delete(@PathVariable("id") Long id) {
    this.userRepository.deleteById(id);
  }
}
```

@RestControllerでこのクラスがREST APIのコントローラークラスであることを示します。このアノテーションを付与することで、HTTPリクエストを受け取り、レスポンスを返すためのメソッドが提供されます。

@RequestMappingのアノテーションでエンドポイントの共通部分を設定します。

あとは@GetMappingや@PostMappingなどそれぞれHTTPリクエストに応じたエンドポイントを作成するためのアノテーションを付与しメソッドを作成していきます。

## 動作確認

ここまで実装できたら再度アプリケーションを実行して動作確認をしてみましょう。

### ユーザー追加

2件ほどユーザーを追加してみます

```
curl -X POST -H "Content-Type: application/json" -d '{"name":"USER_001", "email":"user001@example.com"}' localhost:8080/api/users
curl -X POST -H "Content-Type: application/json" -d '{"name":"USER_002", "email":"user002@example.com"}' localhost:8080/api/users
```

データベースを確認してユーザーが追加されているか確認してみましょう。
```sql
mysql> select * from users;
+----+----------+---------------------+
| id | name     | email               |
+----+----------+---------------------+
|  1 | USER_001 | user001@example.com |
|  2 | USER_002 | user002@example.com |
+----+----------+---------------------+
2 rows in set (0.00 sec)
```

### ユーザー取得

全件取得
```
% curl -X GET localhost:8080/api/users
[{"id":1,"name":"USER_001","email":"user001@example.com"},{"id":2,"name":"USER_002","email":"user002@example.com"}]
```

IDを指定して取得
```
% curl -X GET localhost:8080/api/users/1
{"id":1,"name":"USER_001","email":"user001@example.com"}
```

### ユーザー更新

更新
```
% curl -X PUT -H "Content-Type: application/json" -d '{"name":"USER_001_UPDATE", "email":"user100_update@example.com"}' localhost:8080/api/users/1
```
更新されたか確認
```
% curl -X GET localhost:8080/api/users/1
{"id":1,"name":"USER_001_UPDATE","email":"user100_update@example.com"}
```

### ユーザー削除

削除
```
% curl -X DELETE localhost:8080/api/users/2
```

削除されたか確認

```
% curl -X GET localhost:8080/api/users
[{"id":1,"name":"USER_001_UPDATE","email":"user100_update@example.com"}]
```

## 最後に
SpringBootでREST APIを実装してみました。
最短でREST APIを体験するための実装になっているので、エラーハンドリングとかビジネスロジックとか全然書いていないので、そこはご了承ください...。

また、今回実装した内容はこちらにあります。

https://github.com/yunosuke-github/sample-api