SpringBoot + MongoDB を使ってREST APIを作ってみよう

# この記事は何？

データストアにMongoDBを利用し、SpringBootを使ったREST APIを構築するハンズオン用ページです。
研修用の突貫なので、簡単に順次手順をまとめ、写経することで簡単に体感できることを目標にします。

内容自体はほぼ下記のコピペです。Gradleを使いたかったのでちょっと内容変えています。
https://stackabuse.com/spring-data-mongodb-tutorial/

# 前提

* Javaを触ったことがあること
* IntelliJ IDEAを使えること
* REST APIとは何かを知っていること
* Postmanを使えること
    * この辺り参考にしてください：https://qiita.com/notch0314/items/97c7d1bb565fdae4a712
* NoSQL・MongoDBについて知っていること

# 実装概要

SpringBootを使ってREST APIを作ります。
データストアにMongoDBを使用し、リクエストに応じてMongoDBのデータをCRUDします

完成物は下記GitHubにあげていますので、上手く行かないところなどあれば参考にしてください。
https://github.com/theMistletoe/MongoDB-SpringBoot-sample

# ハンズオン手順

## MongoDBのセットアップ

MongoDBにサインアップする
https://account.mongodb.com/account/register

サインアップしたら「Build a Cluster」をクリック

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114040/772e4fef-5a42-de71-04c3-b19221c14230.png)

無料でやりたいのでFREEを選びましょう

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114040/d25e7d19-b2bb-62ab-fb77-20c00cc814bb.png)


clusterの設定。特に変えなくても良いです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114040/a0d8a8dd-545c-d889-5df1-53f1d747f2cd.png)


cluster構築が始まります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114040/7e4ae200-d49a-83d2-8e78-8649655855c5.png)


cluster構築が完了したら、まずDatabaseアクセス用のユーザーを作ります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114040/2e87e3a7-5f4f-ae95-43a1-aee57cefa817.png)


パスワード認証を選択して、ユーザー名・パスワードを入力してユーザーを作成する。
あとでこのユーザー名・パスワードは使うので覚えておいてください。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114040/e8d3ffe1-766f-b097-4857-4446dd9d921c.png)


ユーザーができた

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114040/5494d465-de42-b9e9-b90a-a869755c94f0.png)


次にNetworkAccessを設定します。
IPアドレスを使ったホワイトリストを定義できるので、練習用に任意のIPアドレスを受けいれるよう設定しましょう。
（練習用にセキュリティゆるゆるなので、本番での利用はやめてください）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114040/243ec0f7-9425-d8dc-33df-52a5a9f8a015.png)

[ALLOW ACCESS FROM ANYWHERE]をクリックしてConfirmしましょう

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114040/a7adf765-784a-ef00-031a-ae19a1a67075.png)


ではDB作っていきましょう
ClustersのCOLLECTIONSをクリック

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114040/2275ac14-de6b-21b4-0d6d-f18bc93bed07.png)

[Add My Own Data]をクリック

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114040/b97c3b56-a50b-82c4-94b6-bca3487a798e.png)


データベース名に「jobs」、コレクション名に「candidate」を入力しCreateしましょう

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114040/36f1b34c-3181-1b1a-8983-80b9e2a66109.png)

データベース作成後、コンソール画面が表示されます。
のちのデータ更新についてはここに反映されていきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114040/75a9e400-8b91-47cc-64a4-85f00127a911.png)

一旦ここまででMongoDBのセットアップおしまいです。


## SpringBootの環境構築

[Spring Initializr](https://start.spring.io/)にアクセスして、テンプレートを画像の通り設定して、Generateでzipをダウンロードしましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114040/3dd76cf1-0260-513e-130c-bef0fef991f9.png)

zipを解凍して、IntelliJで開きましょう。
まずはapplication.propertiesを開いて、MongoDBのアクセス情報をセッティングしましょう

```properties

spring.data.mongodb.uri=mongodb+srv://user:<password>@cluster0.ayoho.mongodb.net/<dbname>?retryWrites=true&w=majority
```

spring.data.mongodb.uriへの設定値についてはMongoDBコンソールから取得できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114040/d441b5f5-ca3c-a2e5-e86a-53e5ea2dbac1.png)


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114040/17f73d6a-df63-cfcc-a487-be75313c4bae.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114040/f3df74b5-7c77-b37e-50eb-cc356e82f554.png)

passwordとdbnameについてはMongoDBセットアップ時に設定したものを使いましょう
（手順に従っていればdbnameは「jobs」になります）

## Javaプログラム実装

あとはJavaを書いていきましょう。
細かい実装の解説はいれませんので、不明点は解消しながら書いていってみてください。
配置場所はどこでもいいんですが、package情報を参照いただくか、GitHubを参考にしてみてください。



```java:Candidate.java
package com.example.mongoDBsample.entity;

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.Document;

@Document(collection = "candidate")
public class Candidate {
    @Id
    private String id;

    private String name;

    private double exp;

    @Indexed(unique = true)
    private String email;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getExp() {
        return exp;
    }

    public void setExp(double exp) {
        this.exp = exp;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}
```

```java:CandidateRepository.java
package com.example.mongoDBsample.repository;

import com.example.mongoDBsample.entity.Candidate;
import org.springframework.data.mongodb.repository.MongoRepository;

import java.util.List;
import java.util.Optional;

public interface CandidateRepository extends MongoRepository<Candidate, String> {
    Optional<Candidate> findByEmail(String email);

    List<Candidate> findByExpGreaterThanEqual(double exp);

    List<Candidate> findByExpBetween(double from, double to);
}
```

```java:CandidateController.java
package com.example.mongoDBsample.controller;

import com.example.mongoDBsample.exception.ResourceNotFoundException;
import com.example.mongoDBsample.entity.Candidate;
import com.example.mongoDBsample.repository.CandidateRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.List;

@RestController
@RequestMapping("/candidate")
public class CandidateController {

    @Autowired
    private CandidateRepository candidateRepository;

    @PostMapping
    @ResponseStatus(code = HttpStatus.CREATED)
    public Candidate add(@RequestBody Candidate candidate) {
        return candidateRepository.save(candidate);
    }

    @GetMapping
    public List<Candidate> getAll() {
        return candidateRepository.findAll();
    }

    @GetMapping(value = "/{id}")
    public Candidate getOne(@PathVariable String id) {
        return candidateRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException());
    }

    @PutMapping(value = "/{id}")
    public Candidate update(@PathVariable String id, @RequestBody Candidate updatedCandidate) {
        Candidate candidate = candidateRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException());
        candidate.setName(updatedCandidate.getName());
        candidate.setExp(updatedCandidate.getExp());
        candidate.setEmail(updatedCandidate.getEmail());
        return candidateRepository.save(candidate);
    }

    @DeleteMapping(value = "/{id}")
    @ResponseStatus(code = HttpStatus.ACCEPTED)
    public void delete(@PathVariable String id) {
        Candidate candidate = candidateRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException());
        candidateRepository.delete(candidate);
    }

    @GetMapping("/searchByEmail")
    public Candidate searchByEmail(@RequestParam(name = "email") String email) {
        return candidateRepository.findByEmail(email)
                .orElseThrow(() -> new ResourceNotFoundException());

    }

    @GetMapping("/searchByExp")
    public List<Candidate> searchByExp(@RequestParam(name = "expFrom") Double expFrom, @RequestParam(name = "expTo", required = false) Double expTo) {
        List<Candidate> result = new ArrayList<>();
        if (expTo != null) {
            result = candidateRepository.findByExpBetween(expFrom, expTo);
        } else {
            result = candidateRepository.findByExpGreaterThanEqual(expFrom);
        }
        return result;
    }
}
```

```java:ResourceNotFoundException.java
package com.example.mongoDBsample.exception;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(value = HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {
    public ResourceNotFoundException() {
    }
}
```

## 実装の確認

これで実装完了なので、データアクセスがAPI経由でできるかどうかを確認してみましょう。

IntelliJでアプリを起動しましょう

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114040/4f895c8a-f151-f266-ecea-b32c81acfff0.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114040/85c130be-411d-5500-39ae-bb894c7f8bc9.png)


あるいはCUIで下記を実行して起動する

```bash
./gradlew bootRun
```

### ユーザーを登録する


まずはユーザーを登録しましょう。
POSTリクエストを http://localhost:8080/candidate/ に下記JSONオブジェクトをbodyにつけて投げると、
登録ユーザーの情報がレスポンスに返ってきます。

```json
{
    "name": "Axel",
    "exp": "10",
    "email": "axel@example.com"
}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114040/de4f8263-a21f-3119-ce63-bddb5d17e64c.png)

MongoDBのコンソールを見ると、データが追加されていることが確認できる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114040/46a2a73d-c917-4119-cb45-08fa7a8fe71a.png)


### ユーザーを参照する

GETリクエストをhttp://localhost:8080/candidate に投げると登録ユーザーが全件返ってきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114040/f481db34-e176-78c4-40c0-588c284f14b1.png)


### ユーザー情報を更新する

PUTリクエストで http://localhost:8080/candidate/{ユーザーid} に
更新後のJSONオブジェクトをbodyにつけると、登録情報が更新されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114040/1305ff9a-e715-f233-b64e-9223f1e0a8c7.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114040/e59d8414-5b4f-7eb5-49a3-a350158def6b.png)


### ユーザーを削除する


DELETEリクエストを http://localhost:8080/candidate/{ユーザーid} に投げることで
ユーザーを削除できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114040/7201ee4b-1883-16d2-83c9-976a8f925559.png)


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114040/e654b168-11d4-78fa-8ad0-d799e990a5da.png)

---

以上です。

MongoDB/SpringBootを作ってデータストアを利用できるREST APIのバックエンドの基礎を実装できたと思います。
これを拡張したりフロントエンドと連携してアプリケーションを作ってみましょう！