Backend Engineerに贈る初めてのREST APIのURI設計

# はじめに
こんにちは @yuto_1220 です。今回はBackend Engineerに向けてREST APIにおけるURI設計について記述しようと思います。自身が最近経験したプロジェクトにおいて、それまでよく知らなかったREST APIのURIの設計について学んだので備忘録も兼ねて記述しようと思いました。

# HTTPリクエストメソッドについて
REST APIはHTTP通信によって実現されるAPIですが、このHTTP通信は、いくつかの**メソッド**を持っており、それらのメソッドの指定によって、そのAPIが**どのような操作を行いたいのか**が一目で分かるようになっています。例えば、リソースの取得を行いたいのであれば`GET`を用います。

## `GET`
リソースの一覧を取得したり、特定のリソースの取得などを行います。

## `POST`
データの送信を行います。それによって、新しいリソースの作成を行ったりします。

## `DELETE`
指定したリソースを削除します。

## `PATCH`
リソースを**部分的**に変更します。

## `PUT`
特定のリソースを送られたデータで**置換**します。つまり、リソースの作成 or 更新となります。

## `PATCH`と`PUT`の違いについて
`PATCH`と`PUT`は一言で言うと**リソースの更新**を行うメソッドであると一般的に言われます。そのこと自体は間違っていませんが、それぞれのメソッドは更新対象が少し違います。
例として以下のような`itemId: 1`のリソースがあるとします。（リソースという概念は曖昧であり色々あるかと思いますが、今回は例としてjsonとして記述しています。）

```json:resource.json
"pen": {
    "length": 10,
    "color": "black",
    "owner": "taro"
}
```

このリソースのcolorをredに変更してみます。
`PATCH`で適切にリクエストを送る場合は以下のようになります。

```terminal
$ curl -X PATCH -H "Content-Type: application/json" \
    -d '{"pen": {"color":"red"}}' \
    https://dummy-host.com/items/1
```

では、次は`PUT`で適切なリクエストを送ってみましょう
```terminal
$ curl -X PUT -H "Content-Type: application/json" \
    -d '{"pen": {"length": 10, "color": "red", "owner": "taro"}' \
    https://dummy-host.com/items/1
```

これで`itemId: 1`のリソースは意図した通りにcolorがredになりました。
```json:resource.json
"pen": {
    "length": 10,
    "color": "red",
    "owner": "taro"
}
```

では、今度はこれを逆にしてみましょう。
本来は`PATCH`で送るべきリクエストを`PUT`で送るとリクエストとその結果は以下のようになります。

```terminal
$ curl -X PUT -H "Content-Type: application/json" \
    -d '{"pen": {"color":"red"}}' \
    https://dummy-host.com/items/1
```
```json:resource.json
"pen": {
    "color": "red"
}
```

`length`と`owner`が無くなってしまいました。PUTは**置換**なのでこうなりますね。これは意図した結果ではありません。

次に、本来は`PUT`で送るべきリクエストを`PATCH`で送ってみます。そのリクエストと結果は以下のようになります。

```terminal
$ curl -X PATCH -H "Content-Type: application/json" \
    -d '{"pen": {"length": 10, "color": "red", "owner": "taro"}' \
    https://dummy-host.com/items/1
```
```json:resource.json
"pen": {
    "length": 10,
    "color": "red",
    "owner": "taro"
}
```

上手く行ったように見えますね。`PATCH`はリソースの部分的な変更ですが、リソース全体の変更としても上手くいく場合もあると思います。ですが、サーバーによっては`PUT`と`PATCH`の違いを認識しそれぞれのメソッドで適切に処理することがあります。なので、適切にHTTPメソッドを指定する必要がありますし、APIの使用者に上手く意図が伝わらないこともあります。

# URI設計の基本
## {リソース名}s/{リソースid}
REST APIでは特定のリソースを指定する時は`{リソース名}s/{リソースid}`の形になります。`DELETE`メソッドで`itemId: 1`のリソースを削除したい場合は以下のようになります。

```terminal
$ curl -X DELETE https://dummy-host.com/items/1
```

また、`itemId: 1`の中の`partId: 2`のpartを削除したい場合は、`{リソース名}s/{リソースid}`に従うと以下のようになります。

```terminal
$ curl -X DELETE https://dummy-host.com/items/1/parts/2
```

この場合はitemとpartは**親子関係を持っていることが必須**になります。上記の例ではitemとpartの関係は1:Nを想定しています。

また、itemとpartの関係が1:1の場合であれば、以下のようなURIでもpartを特定して削除することができます。

```terminal
$ curl -X DELETE https://dummy-host.com/items/1/part
```
**注意点としてはモデル間に適切な1:Nや1:1関係がないとこれらのようなURIにはなりません。**

例えば、itemとsportの関係には特に何も関係が無いと仮定します。その場合は以下のようなURIは**成り立たない**ということですね。

```terminal
$ curl -X DELETE https://dummy-host.com/items/1/sports/2
```
`itemId: 1`の中にsportsが無いのであればそもそも不自然なURIですね。（本当にこのモデル関係が存在するのであればもちろん成り立ちますが。）

## 各場面でのURI設計例
### リソースの一覧の取得
このエントリポイントでは`/{リソース名}s`になります。つまり、itemのリソースの一覧を取得したい場合は以下のようになります。

```terminal
$ curl -X GET https://dummy-host.com/items
```

### 特定のリソースの取得
このエントリポイントでは、`{リソース名}s/{リソースid}`を用いることになります。つまり`itemId: 1`のリソースを取得する場合は以下のようになります。

```terminal
$ curl -X GET https://dummy-host.com/items/1
```

### 特定のリソースの検索
このエントリポイントではクエリパラメータを用います。また、基本的な概念としては**検索 = 条件で絞り込んだ一覧**です。例として、colorがblackのitemを検索してみましょう。

```terminal
$ curl -X GET https://dummy-host.com/items?color=black
```

これはよく見かける形式だと思います。もちろん、クエリパラメータは条件による絞り込みのためだけに存在しているわけではありませんが、検索のエントリポイントを設計する際にはこのように用いるのがベターです。

### リソースの新規作成
リソースを新規作成、つまりItemモデルの新しいリソースを作成するエントリポイントは、基本的には`/{リソース名}s`のようになります。itemの新規リソースを作成したい場合は以下のようになります。リソースの新規作成なのでメソッドは`POST`ですね。

```terminal
$ curl -X POST -H "Content-Type: application/json" \
    -d '{"pen": {"length": 10, "color": "red", "owner": "taro"}' \
    https://dummy-host.com/items
```

#### リソースの新規作成のバリエーション
itemを新規作成したい場合は`POST /items`のURIを作成してあげれば良いことは分かりました。

ですが、例えば「既存のitemから複製して作成したい場合」や「色や持ち主を受け取ってitemを新規作成したい場合」があると仮定します。
これらのitemを作成するロジックはそれぞれ多少異なっていることが多いのでは無いかと思います。なので、ロジックは「複製での作成」なのか「受け取った入力からの作成」なのかは重大な関心事です。これを判別するには、一例としてリクエストボディで受け取ると良いかと思います。

複製の場合は

```terminal
$ curl -X POST -H "Content-Type: application/json" \
    -d '{"itemId":1}' \
    https://dummy-host.com/items
```

`itemId`だけを受け取れば、ロジックはそれを既存のitem（この場合は`itemId: 1`）の複製だと見なします。

入力を受け取った場合は

```terminal
$ curl -X POST -H "Content-Type: application/json" \
    -d '{"color":"black", "owner": "taro"}' \
    https://dummy-host.com/items
```

`itemId`ではなくitemリソースを構成する要素を受け取った場合は入力からの作成だとロジックは見なします。
何が言いたいのかというと**APIを提供するサービス内でitemを作成するエントリポイントはここだけにすべきだと言うことです。**

:::note warn
APIを提供するサービスがRuby on Railsで実装されている場合は、itemを作成するエントリポイントは`POST /items`でコントローラーとしては`/app/controllers/api/v1/items_controller.rb`内の`create`メソッドで実装されると思います。これは多くの場合においては正しいと思います。

ただ、油断をすると「itemを新規作成する」エントリポイントがアプリケーション中に散見されることがあります。

例えば、既存のitemの複製から作成する場合は`POST /duplications`で`/app/controllers/api/v1/duplications_controller.rb`の`create`メソッドに記述されたり、すでにサービス内部にある作成者の情報を使用して作成する場合に`POST /maker-items`のエントリポイントで`app/controllers/api/v1/maker_items_controller.rb`の`create`メソッドを実装してしまうことがあります。

そのエントリポイントが**最終的にitemを新規作成したい**のであれば、基本的には全て`POST /items`で処理すべきです。同じ入り口を用意し同じロジックを用いなければ不要な重複ロジックを生んだり、他の開発者が意図を理解できないことがあります。APIの処理は**どこからやってくるのかではなく最終的には何をしたいのか**で考えるとURIやロジックの置き場所が正しくなりやすいです。
:::

### リソースの更新
[上述](#patchとputの違いについて)したように更新にはいくつかの方法があります。1つのリソースを特定して**部分的**に更新する場合を考えてみます。この場合は以下のように`{リソース名}s/{リソースid}`で特定のリソースを指定し`PATCH`でリクエストを送るようにするのがベターだと思います。

```terminal
$ curl -X PATCH -H "Content-Type: application/json" \
    -d '{"pen": {"color":"red"}}' \
    https://dummy-host.com/items/1
```

リソースを置換して更新したい場合は以下のようになります。

```terminal
$ curl -X PUT -H "Content-Type: application/json" \
    -d '{"pen": {"length": 10, "color": "red", "owner": "taro"}' \
    https://dummy-host.com/items/1
```

### 削除
`{リソース名}s/{リソースid}`のようにリソースを特定し、`DELETE`メソッドでリクエストを送ります。

```terminal
$ curl -X DELETE https://dummy-host.com/items/1
```

ちなみに、`itemId: 1`の中の`partId: 2`のリソースを削除したいのであれば以下のようになると思います。

```terminal
$ curl -X DELETE https://dummy-host.com/items/1/parts/2
```

### 一括でのリソースの更新 & 削除
これにはいくつかの方法があるかと思いますが、[こちら](https://gist.github.com/hypermkt/2536ac90b006bc1042d01eab0a3cae88)を参考にされると良いかと思います。こちらの記事にあるような方法を取るか、もしくは適切にURIやロジックを設定できるのであれば他の方法もあったりするのかなと、私もこのことについては最終的な結論を出せていません。

### 上記以外の操作について
上記までは一般的なCRUD操作のエントリポイントについて記述してきましたが、これに当てはまらないエントリポイントも多いのではないかと思います。例えば`itemId: 1`のものをpdfファイルとして取得したい場合は、以下のようになるかと思います。

```terminal
$ curl -X GET https://dummy-host.com/items/1/pdf
```

この辺りはフロントエンド側との意思疎通が取れてたりすれば、ある程度自由にできると思います。参考となるのは[GoogleのAPI設計ガイド](https://cloud.google.com/apis/design?hl=ja)です。

## HTTPメソッド指定での例外
以上でHTTPメソッドのルールを説明してきましたが、例外もあります。例えば以下のように`POST`でリソースを**取得**するような場合です。
- 大量のデータ送信: `GET`リクエストはURLの長さに制限があるため、大量のデータを送信する必要がある場合には`POST`が適しています。
- 機密情報の送信: `GET`リクエストのパラメータはURLに含まれるため、ログやブラウザの履歴に残りやすいです。機密情報を扱う場合は、`POST`メソッドを使って情報をリクエストボディに含めることで、より安全にデータを送信できます。

このような例外もありますが、基本的にはルールに則った形でメソッドを定義しましょう。

# まとめ
今回はREST APIのURI設計について記述しました。また、[参考](#参考)にはGoogleのAPI設計ガイドのリンクを貼っているので、そちらもぜひ参照してみてください。最後に自身が最近見た中でも原則に則った良いURI設計がなされているのは[クラウドサイン WebAPI](https://app.swaggerhub.com/apis/CloudSign/cloudsign-web_api)です。これはURIやHTTPリクエストメソッドを見れば何をどうする操作なのかが一目瞭然であり、分かりやすく使いやすいURIの設計だと思います。理解しやすいURIは開発やデバッグを容易にし、不要なコミュケーションコストを削減してくれます。

# 最後に
人それぞれ思想はあると思いますので、絶対的に正しいわけではありません。ただ、基本的にはこんな感じになるかとは思います。

# 参考
Google API 設計ガイド： https://cloud.google.com/apis/design?hl=ja
MDN HTTP リクエストメソッド： https://developer.mozilla.org/ja/docs/Web/HTTP/Methods