【入門】Web API設計の基本をまとめてみた

## はじめに

普段はソフトウェアエンジニアとして働いています。最近Web APIの設計をまるっと考える機会があったので、改めてWeb APIの設計についてまとめてみることにしました。今回扱うAPIは、HTTPプロトコルを使用したRESTful APIです。

そもそもRESTful APIって何だっけ、というところから、API設計の際に意識すべきポイントまで書いてみました。

## 対象読者

Web APIの設計をやったことが無い方、これから設計する必要がある方

## RESTful APIとは

RESTとは、Webサービスの設計思想の一つです。そして、RESTの原則に従って設計されたAPIがRESTful APIです。この原則に従って設計することで、シンプルで使いやすいAPIを作ることができます。

超ざっくりとした一般的な説明だけ載せておきます。↓

### RESTの原則

**クライントとサーバーの分離**

クライアントとサーバーは完全に分離している必要があります。
これにより、クライアントとサーバーは互いを意識せずに開発可能となります。

**ステートレス**

セッション管理を完全にクライアントで行います。これにより、リクエストは他のリクエストから完全に独立して処理されます。

**キャッシュ可能性**

サーバーの応答時間を改善するために、クライアントはクライアントまたは仲介者にレスポンスを保存(キャッシュ)しそれを利用することができます。

**統一インターフェース**

統一された規格で情報をやり取りする必要があります。今回であればHTTPというプロトコルでやりとりします。

**アドレス可能性**

すべてのリソースは一意の識別子(URI)で表現される必要があります。

例えば、qiitaのブログの記事は、以下のようにユーザーidと記事のidによって記事(リソース)が一意に特定されます。

`https://qiita.com/{ユーザーid}/items/{記事のid}`

**オンデマンドのコード**

サーバーはソフトウェアプログラミングコードをクライアントに転送することにより、クライアント側の機能を一時的に拡張またはカスタマイズできます。


## API設計の流れ

本題に入っていきます。

APIを設計する際、まずはどのリソースに対してどのようなリクエストを送り、その結果どのようなレスポンスが返ってきてほしいのかを考えます。

## リクエスト

HTTPリクエストは、以下の3つで構成されています。

```
---------- リクエストライン ------------

POST /items HTTP/1.1

---------- リクエストヘッダー ----------

Accept: image/gif, image/jpeg, */*
Accept-Language: ja
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (Compatible; MSIE 6.0; Windows NT 5.1;)
Host: www.xxx.zzz
Connection: Keep-Alive

---------- リクエストボディ ------------

{
    name: 'hogehoge',
    detail: 'piyopiyo',
}
```

* リクエストライン
    * HTTPメソッド
    * URI
    * HTTPバージョン
* リクエストヘッダー
    * リクエストについての情報や属性(メタデータ)
* リクエストボディ
    * 処理に必要な情報

この中で、今回は**HTTPメソッド、URI、リクエストボディ**をどのように設計すべきかを説明していきます。

HTTPメソッドとURIはリクエストラインに含まれます。
例えば以下のようなものです。商品(item)情報を取得するAPIを想定してみました。

```
GET /items/1 HTTP/1.1
```

これは、items(商品)のid=1番をGETしてね、というリクエストになります。(というか、そのように設計します)

ここでGETの部分はHTTPメソッド、/items/1の部分はURIと呼ばれます。

HTTPメソッドのURIの組み合わせで、どのリソースに対して何をするのかを表します。

### HTTPメソッド

代表的なHTTPメソッドは以下の4種類です。操作の内容に応じて適切なメソッドを選びます。

|メソッド名||
|---|---|
|GET|リソースを取得する際に用います|
|POST|リソースを追加する際に用います|
|PUT|既存のリソースを更新する際に用います|
|DELETE|リソースを削除する際に用います|

### URI設計

URIは、どのリソースに対して操作を行うのかが一目でわかるように設計します。

↓id=123の商品リソースのURIの例
```
https://example.com/items/123
```
↓id=456の商品についているid=11のレビューのリソースのURIの例
```
https://example.com/items/456/reviews/11
```

**【悪い例】**

* **新規追加や更新で使うURIであっても、URIに動詞を含めるのは極力避けるべきです**
    * 動作はHTTPメソッドで表現しましょう
    * NG

        * `
https://example.com/add-items
`
        * `
https://example.com/update-items/123
`

    * OK
        * `POST https://example.com/items` => 追加
        * `PUT https://example.com/123` => 更新


* **リソースは複数形で書くべきです**
    * NG
         * `
         https://example.com/item
         `

 * **冗長なURLは避けましょう**
    * NG
         * `
     https://example.com/items/list/all
     `
 * **一部の人しか理解できない略語や人間が意味を理解できない文字列などは使用すべきではありません**
 * **サーバー側のアーキテクチャを意識した文字列は使用しないようにしましょう**
     * NG
         * `https://example.com/postgresql/items/123`
 * **名詞はハイフンでつなぐようにしましょう**
     * NG
         * `https://example.com/online_games`
     * * NG
         * `https://example.com/online-games`
  * **大文字小文字が混在しないいようにしましょう**
     * NG
         * `https://example.com/Items`
     * OK
         * `https://example.com/items`


### リクエストと一緒に送るデータの検討

 リクエストを送る際は、一緒に情報を送りたい場合が多いです。例えば、参照したいリソースのid、検索条件に使う文字列、新規にリソースを作成する場合の内容など。

リクエストにデータを載せる方法は主に以下の3種類です。

 * パスパラメータ
 * クエリパラメータ
 * リクエストボディ

#### パスパラメータ

パスパラメータは、例えば以下のような形です。

`
https://example.com/items/12345
`

この場合、12345の部分がパスパラメータに当たります。
リソースを一意に特定するのに必要な情報は、このようにパスの中に含めて送ります。

リソースが階層構造を持っている場合は、階層構造がわかるようなURIにします。

例)　id
`https://example.com/items/12345/reviews/678`

#### クエリパラメータ
 
クエリパラメータは、例えば以下のような形です。
 
 `https://example.com/items?name=apple`

 上記の例だと、name=appleという情報が送られます。
 一例として、検索条件などを送る時に使います。クエリパラメータは省略可能です。

【パスパラメータとクエリパラメータの使い分け】

パスパラメータはリソースを一意に特定するために必要な情報、クエリパラメータは省略可能な情報、一意に特定するためではない情報などを送る場合に使うのが一般的です。

 以下のように、リソースを一意に特定するのに必要な省略できない情報はクエリパラメータに含めるべきではありません。

 * NG  `https://example.com/items?id=123`
 * OK  `https://example.com/items/123`

#### リクエストボディ

POST, PUTなどで追加、更新するデータ等はリクエストボディで送ります。なお、HTTPメソッドがGETの場合はリクエストボディでデータを送ることはできません。パスパラメータ、クエリパラメータを使用しましょう。

```
{
    name: 'ラジオ',
    detail: '防水で軽量なラジオです',
    price: '2000',
}
```

【悪い例】
* **リソースを一意に識別するための情報はリクエストボディではなくパスパラメータで送りましょう**
    * URIはリソースを一意に特定できるようにすべきです
    
NG↓
```
PUT /items
-----
{
    id: 123
    name: '高性能ラジオ',
    detail: '完全防水で軽量なラジオです',
    price: '3000',
}
```

OK↓
```
PUT /items/123
-----
{
    name: '高性能ラジオ',
    detail: '完全防水で軽量なラジオです',
    price: '3000',
}
```

## レスポンス

リクエストに対して、どのようなレスポンスを返すかを設計します。

レスポンスの設計で意識すべきことはいくつかありますが、今回はその中でも特に重要な以下二点に絞って書きます。
* レスポンスヘッダーのステータスコード
* レスポンスボディ

### ステータスコード

HTTPレスポンスでは、ステータスコードでリクエストの処理結果を表します。404 Not Found などが有名かと思います。

レスポンスは大きく以下の5種類に分類されます。

* 情報レスポンス (100 – 199)
* 成功レスポンス (200 – 299)
* リダイレクトメッセージ (300 – 399)
* クライアントエラーレスポンス (400 – 499)
* サーバーエラーレスポンス (500 – 599)

代表的なステータスコードを列挙してみます。自分が開発している時に頻繁に見るものを中心に並べました。

|ステータスコード|説明|
|---|---|
|200 OK|リクエストが成功したことを示します。|
|400 Bad Request|クライアント側のエラーで、不正なリクエストが送られたことにより処理ができない場合を示します。|
|401 Unauthorized|認証されていないことを示します。|
|403 Forbidden|アクセス権がないことを示します。|
|404 Not Found|リクエストされたリソースを発見できないことを示します。|
|429 Too Many Requests|一定時間内に大量のリクエストが送信されたことを示します。|
|500 Internal Server Error|サーバー側で処理できなかったことを示します。|
|503 Service Unavailable|サーバー側がダウンしているなどの原因でリクエストを受け取る準備ができていないことを示します。|

処理結果に応じて適切なステータスコードを返してあげることが重要です。

ステータスコードはほかにもたくさんあります。詳しくはこちら↓
https://developer.mozilla.org/ja/docs/Web/HTTP/Status

### レスポンスボディ

今回は一般的なケースとして、JSON形式のレスポンスを考えます。

例えば、`GET items/123` に対するレスポンスを考えます。id=123の商品情報を取得したいとします。
各商品は、id、商品名、商品説明、レビューのリストを持っているとします。その場合、レスポンスは以下のようになります。

```
{
 "id": 123,
 "name": "ワイヤレススピーカー",
 "detail": "高音質で完全防水のスピーカーです。"
 "reviews": [{
         "id": 1,
         "userId": "001",
         "comment": "買って良かったです。"
     },{
         "id": 2,
         "userId": "011",
         "comment": "すぐに壊れました。"
     }]
}
```

【悪い例】
* **クライアントが必要な情報を得るために何度もリクエストを送る必要がある**
    * クライアントが必要なデータを極力少ない回数のリクエストで得られるように設計しましょう
* **複雑な階層構造になっている**
    * できるだけシンプルなレスポンスになるように意識しましょう
    * NG↓ (この場合、info の階層は無い方がシンプルでしょう)
    ```
    {
      id: 123,
      info: {
          name: "ワイヤレススピーカー",
          detail: "高音質で完全防水のスピーカーです。"
          reviews: [
              {
                 "id": 1,
                 "userId": "001",
                 "comment": "買って良かったです。"
              },
              {
                 "id": 2,
                 "userId": "011",
                 "comment": "すぐに壊れました。"
              }
          ]
      }
    }
    ```
    <br>
* **わかりにくい、長い、統一されていない命名**
    * 名称は簡潔、だれが見ても理解できる、一貫性のある命名を心掛けましょう

### エラー時のレスポンス設計 

エラーが発生した時、レスポンスボディにどのような情報を載せればよいか検討します。

ステータスコードによって大まかなエラーの原因を示すことはできますが、それだけだとクライアント側でエラーに対処するには情報が不十分な場合が多いです。なので、基本的にエラーが発生した場合は、適切なステータスコードと共にエラーの詳細をレスポンスボディに載せるのが良いと思います。

独自のエラーコードを定義してレスポンスに含めると、クライアント側でのエラーハンドリングが容易になります。

```
{
  errorCode: 'item_not_found'
  errorMessage: 'Cannot find the item.' 
}
```

## 終わりに

以上が、Web APIを設計する際に意識すべきことの基本になります。この記事が今後API設計をする際の参考になればうれしいです。

最後までお読みいただきありがとうございました。