実践：はじめてのWebAPI設計

## はじめに

この記事はAPIの基本的な実装方法を丁寧に解説します。基礎を学びたい方、今更聞けないような知識の振り返りを求める方の役に立つことを願っています。もう十分理解できている！という方は、目次から`実装`にとんでみてください。

具体的にはHTTPと呼ばれる通信方法を利用した、シンプルな本の貸し出しシステムの土台を考えます。要件の各ステップで、設計の基本原則やベストプラクティスについても触れながら、より実践的な知見を共有できればいいなと思います。

:::note
弊社Nucoでは、他にも様々なお役立ち記事を公開しています。よかったら、Organizationのページも覗いてみてください。
また、Nucoでは一緒に働く仲間も募集しています！興味をお持ちいただける方は、[こちら](https://www.recruit.nuco.co.jp/?qiita_item_id=7dab01ac2ea08b85fb15)まで。
:::
https://www.recruit.nuco.co.jp/?qiita_item_id=7dab01ac2ea08b85fb15

## 基本用語

Webに関する基礎知識の解説記事はQiitaに豊富にあったので、要点を抑えつつリンクをまとめました。

### WebAPI

WebAPIは、ウェブ上での情報のやり取りを行うためのインターフェースです。HTTPなどの通信プロトコルを介してデータを送受信することで、異なるシステムやアプリケーション間での情報共有を可能にします。

代表的な例としてスマホアプリがあります。ユーザーがアプリ上で何らかの情報をリクエストすると、APIを介してその情報をサーバから取得する仕組みです。このプロセスは高速に行われ、かつユーザーには透明であるため、普段は意識することはないかもしれません。

https://qiita.com/NagaokaKenichi/items/df4c8455ab527aeacf02

### HTTP

HTTPとは、WebサーバとWebクライアントの間でデータの送受信を行うために用いられるプロトコル（通信方法）です。簡単に言うとウェブページや画像、動画などの情報を取得したり、フォームに入力したデータをサーバに送信する際の「通信のルール」を定めたものです。もしこのようなプロトコルが存在しないと異なるシステム間のスムーズな情報共有は難しいです。

https://qiita.com/Sekky0905/items/dff3d0da059d6f5bfabf

> **HTTPS**：
> - HTTPにセキュリティ機能（Secure）を追加したプロトコル
> - 通信内容の暗号化を行い、中間者の攻撃やデータの傍受を防ぐ
> - URLが「https://」で始まるウェブサイトは、このHTTPSプロトコルを使用しており、ブラウザのアドレスバーには鍵のアイコンや「安全」と表示がされることが多い

#### リクエスト

クライアントがサーバに対して何らかのアクションを要求するための手段です。

**1.リクエスト行**

HTTPリクエストの最初の行であり、クライアントがサーバに対して要求するアクションの情報を持っています。

まず**メソッド**を考えます。これは行いたい操作（リクエスト）についてのカテゴリーを表しています。以下はメソッドの主な種類と概要です。

| 種類 | 概要 |
|------------|-----|
| GET | リソースの取得。サーバーから情報を取得するためのメソッド。 |
| POST | リソースの作成。新しいリソースをサーバーに送信して作成するためのメソッド。 |
| PUT | リソースの更新。既存のリソースを全体的に更新するためのメソッド。 |
| PATCH | リソースの部分的な更新。既存のリソースの特定の部分だけを更新するためのメソッド。 |
| DELETE | リソースの削除。指定したリソースをサーバーから削除するためのメソッド。 |

https://qiita.com/fukuma_biz/items/a9e8d18467fe3e04068e

次に**URI**（Uniform Resource Identifier）を考えます。

インターネット上のリソースを一意に識別するための文字列であり、位置を示すURL（Uniform Resource Locator）と名前を表すURN（Uniform Resource Name）の二つのサブカテゴリがあり、一般的に「URI」と言う際、多くの場合はURLを指していることが多いです。

またURLの途中でよく目にする`?`記号で始まるクエリパラメータは、リクエストにおける詳細な情報を指定したり、データのフィルタリングなどを行う際に使用されています。

最後に**HTTPバージョン**です。文字通りHTTPプロトコルのバージョンを示しており、どの機能や仕様による通信が行われるのかを明らかにします。初めて導入されたHTTP/0.9から、現在主流となっているHTTP/1.1、更に最近のHTTP/2やHTTP/3など、いくつか存在します。

これらの"メソッド"、"URI"、"HTTPバージョン"によって、標準的なHTTPのリクエスト行は構成されています。

```sql:例：リクエスト行
GET /qiita/search?title=API HTTP/1.1
```

**2.ヘッダーフィールド**

リクエストに関する追加情報を提供する、キーと値のペアによって構成されています。

- **Host**
リクエストが送信される対象のドメインまたはIPアドレス。HTTP/1.1からは、このヘッダーの使用が必須となっている。

- **User-Agent**
リクエストを行ったクライアント（ブラウザやツール）の情報。サーバーはこの情報を使用して、特定のブラウザやデバイスに最適化されたコンテンツを提供する。

- **Accept**
クライアントが受け入れることができるコンテンツタイプを示す。たとえば、ブラウザがJPEGとPNGの画像のみを表示できる場合、このヘッダーを使用してその情報をサーバーに伝える。

- **Content-Type**
ボディのメディアタイプを示す（主にPOSTやPUTリクエストで使用される）。サーバーがどのようにリクエストボディを解釈すべきかが明らかになる。

```makefile:例：ヘッダーフィールド
Host: 127.0.0.1:5000
User-Agent: curl/7.68.0
Accept: */*
Content-Type: application/json
```

**3.ボディ**

ボディは、リクエストに伴って送信されるデータの実際の内容です。特に、POST や PUT のようなリクエストで、サーバにデータを送信する際に使用されます。最近はJSON形式が主流です。

```json:例：ボディ
{
    "name": "Alice",
    "age": 30,
    "email": "alice@example.com",
    "is_active": true
}
```

#### レスポンス

こちらは反対にサーバーからクライアントに送られる情報のことを指します。

**1.ステータス行**
ステータス行はHTTPレスポンスの最初の行です。
まず、リクエスト行にもあった**HTTPバージョン**の記述から始まります。

次に**ステータスコード**です。3桁の数字で、リクエストの処理結果を示すものです。404（リソースがサーバー上に存在しない）などは特に目にしたことがあるのではないでしょうか。大まかなカテゴリを以下のテーブルに示します。


| 範囲   | カテゴリ                | 説明                                          |
|--------|------------------------|-----------------------------------------------|
| 100番台 | 情報レスポンス       | リクエストは受け取られたが、処理は続行される。       |
| 200番台 | 成功レスポンス       | リクエストが正常に処理された。                      |
| 300番台 | リダイレクトメッセージ | クライアントが追加のアクションを取る必要がある。   |
| 400番台 | クライアントエラーレスポンス | クライアントのリクエストに問題があった場合。    |
| 500番台 | サーバーエラーレスポンス | サーバー側での問題によりリクエストが処理できない。 |

https://qiita.com/uenosy/items/ba9dbc70781bddc4a491

最後に**ステータステキスト**で、人間にとって読みやすい形式の短い文書です。たとえば、ステータスコードが数字のみの200の場合、ステータステキストはOKとなります。同様に、404のステータスコードにはNot Foundというテキストが伴います。

開発者やシステム管理者が迅速にレスポンスの状態を理解するのに役立っています。

```sql:例：ステータス行
HTTP/1.1 200 OK
```

**2.ヘッダーフィールド**

レスポンスに関する追加情報を提供する、キーと値のペアによって構成されています。

- **Date**
レスポンスが生成された日時を示す。

- **Server**
レスポンスを生成したサーバの名前やバージョン情報。

- **Content-Length**
ボディのバイト単位の長さを示す。クライアントはこの情報を使用して、受信したデータが完全かどうかを判断することができる。

- **Content-Type**
ボディのメディアタイプを示す。これによりクライアントはボディの内容を適切に解釈・表示することが可能。

```makefile:例：ヘッダーフィールド
Date: Wed, 14 Oct 2023 09:10:15 GMT
Server: Apache/2.4.38
Content-Length: 1234
Content-Type: application/json
```

**3.ボディ**

レスポンスに伴ってクライアントに送られるデータの実際の内容です。HTML、JSON、画像、音声など、さまざまなメディアタイプのデータを含めることができます。

```json:例：ボディ
{
    "status": "success",
    "message": "ユーザー情報を正常に更新しました。",
    "user": {
        "name": "Alice",
        "age": 30,
        "email": "alice@example.com",
        "isActive": true
    }
}
```

### REST

"**RE**presentational **S**tate **T**ransfer" の略であり、WebAPI開発における設計原則の主流です。
「Web上のすべてのリソース（データやサービスなど）が一意のURIによって識別され、これを用いてそれらにアクセスすることができる」という考え方が軸になっています。

具体的には、大まかに四つに分けられる基本原則が設けられています。

> 1. ステートレスなクライアント/サーバプロトコル
> すべてのHTTPリクエストはそれ自体で完結しており、必要な全ての情報を含んでいる。
> 2. リソースの一意な識別
> 各リソースは独自のURIで表現され、一意にアクセス可能である。
> 3. 統一されたインターフェース
> リソースの操作は、HTTPメソッドを使用して表現される。
> 4. ハイパーメディアの使用
> 関連するデータやアクションは、リンクとしてリソース内に埋め込むことができる。

これらに則ったものがRestfulなAPIと呼ばれています。

https://qiita.com/masato44gm/items/dffb8281536ad321fb08

## 設計の重要性

初めて取り組む段階ではなかなか難しいですが、思いつくままに実装していては開発者にもユーザーにとっても使いにくく、メンテナンスが困難なものになってしまいます。

WebAPIの設計は、仮に時間がかかってもじっくりと検討する価値があります。しっかりとした基盤があれば、それをもとに堅固なシステムを構築していくことができるのです。

- **再利用性**
一度実装したAPIを異なるシステムやアプリケーションで、再び利用することができる。
- **拡張性**
新しい機能やデータを簡単に追加することができ、将来的な要件の変更や拡張に柔軟に対応可能。
- **予測可能性**
開発者はもちろん、ユーザーにとっても期待通りの動作が保証されている。

そして、ユーザーの視点を考慮することも重要です。アーキテクチャや技術的な側面だけでなく、どのようにAPIが利用されるのか、その体験はどうあるべきかを抑えておくことが大切です。

APIとはその名の通りインターフェイスであり、内部のビジネスロジックを露出させずに簡潔な振る舞いが期待されています。

例えば、あるカフェの注文システムを考えてみます。顧客は「アイスコーヒー」を注文したいだけで、どのような在庫管理や計算が背後にあるかを知る必要はありません。その注文に応じて「アイスコーヒーをご注文いただきありがとうございます」と応答することでコミュニケーションが成立します。

親しみをもって利用されて初めて、良いシステムといえるのだと思います。

## 実装

前置きが長くなってしまいましたが、いよいよ実際にWebAPIを開発してみようと思います。

突然ですが、もしIT系の企業にお勤めであれば、オフィスにさまざまな専門書・技術書が置いてあるのを目にするのではないでしょうか。（弊社の本棚もぎっしり！）それらを手動で管理するにはなかなか大変ですよね。思い立ったときに目当ての本を探してみても、なかなか見つからない…なんてことも。

そこでシンプルながら拡張性が高いPythonのフレームワーク"Flask"を使って、本棚管理システムの土台を設計します。

**Step1. 書籍の新規登録**

- タイトル・著者など基本情報の入力〈POST〉

**Step2. 書籍情報の更新**

- 既に登録されている情報の更新〈PUT〉

**Step3. 貸出・返却**

- 貸出状態をリソースの属性に追加
- ステータスの変更〈PATCH〉

**Step4. 図書の検索・一覧表示**

- クエリパラメータによる検索〈GET〉
- フィールドの指定

### 基本構造

お使いの環境で、必要なFlaskライブラリをインストールします。

```bash
pip install Flask
```

まず、main.pyを作成していきます。現時点のディレクトリ構造は以下のようになります。

```
.
├── app/
│   └── main.py
└── requirements.txt
```

このファイルは今回作成する簡易システムの、エントリーポイントの役割を担っています。

```python:main.py
from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello():
    return "こんにちは！"

if __name__ == '__main__':
    app.run(debug=True)
```
- 2行目ではFlaskのインスタンスを作成して、app変数に代入しています。ここで、`__name__`は現在のモジュール名を示しています。

- デコレータ`@app.route("/")`は、URLのルート（例: [http://127.0.0.1:5000/](http://127.0.0.1:5000/)）へのアクセスがあった場合に、次に定義される関数（この場合はhello()）を呼び出すようにFlaskに指示しています。

:::note
`app.run(debug=True)`：デバックモードでの起動
- コードに変更を加えた際に、サーバが自動的に再起動する。
- 何らかのエラーが発生したときに、ブラウザ上で詳細情報やトレースバックを表示する。

セキュリティ上の脆弱性から、開発環境専用のオプションとなっています。
:::

そして、main.pyを実行してみるとターミナルに以下の出力が出ます。

```bash
 * Serving Flask app 'main'
 * Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on http://127.0.0.1:5000
Press CTRL+C to quit
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: ***-***-***
```


`http://127.0.0.1:5000/`にアクセスしてみます。
> Flaskの@app.routeデコレータにおいて、特定のHTTPメソッドを指定しない場合、デフォルトでGETメソッドのリクエストを処理するように設定されています。
```
127.0.0.1 - - [16/Oct/2023 14:55:52] "GET / HTTP/1.1" 200 -
```
左上に`こんにちは！`と表示されると思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3025452/92dc8ec2-3b3c-3469-647d-83d857951b04.png)


また**curlコマンド**を使うことで、簡単にHTTPプロトコルでのデータ送受信を行うことができます。

```bash:送信
curl http://127.0.0.1:5000/
```

```bash:受信
こんにちは！
```

https://qiita.com/yasuhiroki/items/a569d3371a66e365316f

### リソース

REST APIにおける「リソース」とは、**アプリケーション内で識別・管理される情報の単位**を表します。各リソースは一意の識別子を持ち、複数のリソースが存在する場合、それぞれは独立して管理されます。

たとえばAPIがユーザー情報、商品データ、注文履歴といった異なる種類のデータを扱うとき、これらはそれぞれ異なるリソースとして定義されます。

今回のシステムで、中核となるリソースを書籍とします。

```
.
├── app/
│   ├── main.py
│   └── books.py
└── requirements.txt
```

現段階のシンプルな属性は以下の通りです。
- id：一意の識別子（同じ本が二冊あった場合にも、区別が可能）
- title：書籍のタイトル
- author：書籍の著者

```python:books.py
class Book:
    def __init__(self, id, title, author):
        self.id = id
        self.title = title
        self.author = author
```

ここで定義した書籍をまとめて扱うための集合体（**books**）が「コレクションリソース」となります。

そしてリソースに対して操作を行うためのURIを**エンドポイント**と呼びます。例えば、以下の例は特定のidを持つ書籍についての情報を取得しています。

```
$ curl -X GET http://localhost:5000/books/{id}
```
クライアントにとって簡潔で分かりやすい入り口であるために、命名や構造が大きな役割を担っています。

:::note
**エンドポイント設計の基本原則**「覚えやすく、どんな機能を持つURIなのかがひと目でわかる」
- 名詞
- 複数形（book → books）
- 単語はハイフンで繋ぐ（technical_book → technical-books）
:::

https://qiita.com/NagaokaKenichi/items/6298eb8960570c7ad2e9

### 書籍の新規登録

HTTPメソッドの〈**POST**〉を使って、書籍を登録するエンドポイントを定義します。

まず簡易的なデータ保管のため、`books.py`で空のリストを定義しておきます。
```python:books.py
books = []
```

`main.py`のimport文を書き換え、`add_book`メソッドを追加します。

- 受信したデータからオブジェクトを生成し、リストに追加
- IDは一意であることから、登録した順番に番号を振る
- 成功した場合は、新しい本の情報とメッセージをJSON形式で返す

> 201 Created：新たなリソースの作成に成功

```python:main.py
from flask import Flask, request, jsonify
from book import Book, books

@app.route('/books', methods=['POST'])
def add_book():
    data = request.get_json()

    book_id = len(books) + 1

    new_book = Book(id=book_id, title=data.get('title'), author=data.get('author'))
    books.append(new_book)

    return jsonify({"message": "本の登録に成功しました。", 
                    "book": {"id": new_book.id, 
                             "title": new_book.title, 
                             "author": new_book.author}}), 201
```

デバッグモードを起動していれば、ファイルを変更を保存すると自動的にサーバが再起動します。

```bash
* Detected change in './app/main.py', reloading
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: ***-***-***
```

それではJSON形式のボディを作って、HTTPリクエストを行ってみましょう。
```bash:送信
curl -X POST http://localhost:5000/books \
     -H "Content-Type: application/json" \
     -d '{"title": "プログラムはなぜ動くのか", "author": "矢沢 久雄"}'
```
:::note
**コンテンツタイプを有効にする**
- セキュリティ対策
- サーバーがリクエストを受け取る際に、意図しない形式のデータを受け取らないようにする
- JSON形式の場合は、`Content-Type: application/json`で指定
:::
```json:受信
{
    "book": {
        "author": "矢沢 久雄",
        "id": 1,
        "title": "プログラムはなぜ動くのか"
    },
    "message": "本の登録に成功しました。"
}
```

### 書籍情報の更新

〈**PUT**〉メソッドによる書籍の更新をおこなうエンドポイントを定義します。

- 指定されたIDの書籍に対する操作
- リクエストボディから`title`、`author`を取得し、情報を更新

> 404 Not Found：リソースが存在しない


```python:main.py
@app.route('/books/<int:book_id>', methods=['PUT'])
def update_book(book_id):
    data = request.get_json()

    book_to_update = next((book for book in books if book.id == book_id), None)
    
    if not book_to_update:
        return jsonify({"message": "書籍が見つかりません。"}), 404

    if 'title' in data:
        book_to_update.title = data['title']
    if 'author' in data:
        book_to_update.author = data['author']

    return jsonify({"message": "情報を更新しました。",
                    "book": {"id": book_to_update.id,
                             "title": book_to_update.title,
                             "author": book_to_update.author}}), 200
```

サーバを再起動し、リクエストを行います。
```bash:送信
curl -X PUT http://localhost:5000/books/1 \
     -H "Content-Type: application/json" \
     -d '{"title": "プログラムはなぜ動くのか 第3版", "author": "矢沢 久雄"}'
```
書籍が登録されていないと以下のレスポンスを取得します。
```json:受信 <失敗>
{
    "message": "書籍が見つかりません。"
}
```
更新に成功すると、そのメッセージと最新情報を取得します。
```json:受信 <成功>
{
  "message": "情報を更新しました。",
  "book": {
    "author": "矢沢 久雄",
    "id": 1,
    "title": "プログラムはなぜ動くのか 第3版"
  }
}
```
### 貸出・返却

books.pyに書籍の属性に、貸出ステータスを表すbool値`is_borrowed`を追加します。

```python:books.py
class Book:
    def __init__(self, id, title, author):
        self.id = id
        self.title = title
        self.author = author
        self.is_borrowed = False
```

〈**PATCH**〉メソッドによる貸出状態を更新するエンドポイントを定義します。

- 指定されたIDの書籍に対する操作
-  リクエストボディから`action`キーを取得
-  値が`borrow`の場合、ステータスを"貸出中"に更新
-  値が`return`の場合、ステータスを"利用可能"に更新

> 400 Bad Request：不正なリクエスト
> 409 Conflict：操作とリソースの状態の競合

```python:main.py
@app.route('/books/<int:book_id>', methods=['PATCH'])
def update_borrowing_status(book_id):
    book = next((b for b in books if b.id == book_id), None)
    
    if not book:
        return jsonify({"message": "書籍が見つかりません。"}), 404
    
    data = request.get_json()
    
    if 'action' not in data:
        return jsonify({"message": "アクションが指定されていません。"}), 400
    
    if data['action'] == "borrow":
        if book.is_borrowed:
            return jsonify({"message": "この書籍はすでに貸し出されています。"}), 409
        book.is_borrowed = True
        return jsonify({"message": "貸し出しました。"}), 200
    
    elif data['action'] == "return":
        if not book.is_borrowed:
            return jsonify({"message": "この書籍は貸し出されていません。"}), 409
        book.is_borrowed = False
        return jsonify({"message": "返却しました。"}), 200
    
    else:
        return jsonify({"message": "不明なアクションが指定されました。"}), 400
```

それではID1の書籍にアクセスし、貸出の操作を行ってみます。

```bash:送信 <貸出>
curl -X PATCH http://localhost:5000/books/1 \
     -H "Content-Type: application/json" \
     -d '{"action": "borrow"}'
```

:::note
**エラーハンドリング**
- 書籍が存在するか、すでに貸出済みであるかなど様々な異常状態をチェック
    - リクエストエラー：不正な形式
    - 機能的エラー：ビジネスロジックに反している
- システム内の不具合について何が問題であるのか明確になる
- 適切なエラーメッセージによってユーザーエクスペリエンスが向上
:::

**ケース1**：リソースがない

```json:受信 [1]
{
    "message": "書籍が見つかりません。"
}
```

**ケース2**：リクエストボディが空だった


```json:受信 [2]
{
    "message": "アクションが指定されていません。"
}
```

**ケース3**：既に貸出済みだった

```json:受信 [3]
{
    "message": "この書籍はすでに貸し出されています。"
}
```
**ケース4**：`action`の値のスペルミス

```json:受信 [4]
{
    "message": "返却しました。"
}
```

そして正常に貸し出しが行われると、以下のレスポンスを取得します。

```json:受信
{
    "message": "貸し出しました。"
}
```
返却についても同様です。

```bash:送信 <返却>
curl -X PATCH http://localhost:5000/books/1 \
     -H "Content-Type: application/json" \
     -d '{"action": "return"}'
```
```json:受信
{
    "message": "返却しました。"
}
```

### 図書の検索・一覧表示

〈**GET**〉メソッドを利用して検索と表示を行うエンドポイントを定義します。
- 著者・タイトルの部分一致による検索
:::note
**クエリパラメータとパスパラメータ**
- 一意のリソースを表す場合はパスパラメータを利用
```
/books/プログラムはなぜ動くのか
```
- 省略しても良い場合はクエリパラメータ
```
/books?title=プログラム
```
- 検索する際のリクエストは一意にパスが定まらないことが多く、クエリパラメタを利用する
:::
- フィールドを指定した場合、その項目を表示
:::note
**`fields`パラメータを定義**

```
/books?fields=id,title
```
- クライアントにとって必要な情報のみを取得
    - データ量に応じたフロントエンド処理の最適化
- レスポンス時間を短縮
:::
- 該当項目が複数ある場合には件数を取得
- 貸出状況を表すメッセージ

```python:main.py
@app.route('/books', methods=['GET'])
def get_books():
    title_query = request.args.get('title', None, type=str)
    author_query = request.args.get('author', None, type=str)
    fields = request.args.get('fields', None, type=str)

    filtered_books = books
    if title_query:
        filtered_books = [b for b in filtered_books if title_query.lower() in b.title.lower()]
    if author_query:
        filtered_books = [b for b in filtered_books if author_query.lower() in b.author.lower()]

    if not filtered_books:
        return jsonify({"message": "該当する書籍が見つかりません。"}), 404

    response_books = []
    for book in filtered_books:
        status_msg = "貸出中" if book.is_borrowed else "利用可能"
        book_data = {
            "id": book.id,
            "title": book.title,
            "author": book.author,
            "loanStatus": status_msg
        }
        
        if fields:
            fields_list = fields.split(',')
            book_data = {key: book_data[key] for key in fields_list if key in book_data}
        
        response_books.append(book_data)

    response = {"books": response_books}
    if len(response_books) > 1:
        response["count"] = len(response_books)

    return jsonify(response), 200
```
まずクエリパラメータによる検索を行います。
- タイトルに"プログラム"を含む
```bash:
curl -X GET "http://localhost:5000/books?title=プログラム"
```

- 著者の名字に"矢沢"を含む
```bash:
curl -X GET "http://localhost:5000/books?author=矢沢"
```

これらのリクエストをもとに、該当する書籍の情報が得られます。
```json:受信
{
    "books": [
        {
            "id": 1,
            "title": "プログラムはなぜ動くのか",
            "author": "矢沢 久雄",
            "loanStatus": "利用可能"
        }
    ]
}
```
:::note
**JSONのプロパティにキャメルケースを使う**

- `loan_status` → `loanStatus`
- JavaScriptなどのフロントエンド言語において、変換の手間が省ける
- 実際にどの命名規則を採用するかは、システムの要件に応じて決まる
:::

次にフィールドを指定し、一覧表示を行います。
新しく『Web API: The Good Parts』を登録しました。

```bash
curl -X GET "http://localhost:5000/books?fields=id,title,loanStatus"
```
```json:受信
{
    "books": [
        {
            "id": 1,
            "title": "プログラムはなぜ動くのか",
            "loanStatus": "貸し出し中"
        },
        {
            "id": 2,
            "title": "Web API: The Good Parts",
            "loanStatus": "利用可能"
        }
    ],
    "count": 2
}
```

:::note
**レスポンスに件数を含める**
- 大量のデータを持っていた場合の、全体のページネーションをコントロールできる
- クライアントにリソースの総数が伝わり、それをもとにした適切なアクションが可能
:::


## DB接続

今回は書籍情報を一時的にリスト形式で保持していましたが、永続的に管理するためにはデータベースとの接続が不可欠です。

**SQLAlchemy**
- Pythonの代表的なORMライブラリ
    - SQL文を直接記述せずに、通常のオブジェクトを扱うようにデータベースの操作が可能
    - `Flask-SQLAlchemy`によって簡単に統合できる

https://qiita.com/shirakiya/items/0114d51e9c189658002e

## ドキュメンテーション

エンドポイント、リクエスト/レスポンスの形式、使用可能なクエリパラメータなど、システムの詳細をより正確に外部に伝えるためにはAPIドキュメントが必要です。

**OAS（OpenAPI Specification）**

- RESTful APIを記述するための標準的なフォーマット

```yaml:簡易例
openapi: 3.0.0
info: #APIのメタデータ
  version: "1.0.0"
  title: "Library API"
paths: #エンドポイントやメソッド
...
components: #オブジェクトスキーマ
...
```

https://qiita.com/teinen_qiita/items/e440ca7b1b52ec918f1b

このOpenAPI仕様を元に、ドキュメントを自動生成できるツールが複数用意されています。
例えば**SwaggerUI**では、直接APIを呼び出すことも可能です。

## 認証

もちろん誰でもオフィスの本棚を使えるわけではありません。例えば社内の人間のみにサービスを提供する際には、ユーザーの認証が必要です。そこでいくつかの方法を簡単にまとめます。

**ベーシック認証**
- ユーザー名とパスワードを使用
- セキュリティは低くあまり推奨されていない


**APIキー認証**
- サービス提供者がクライアントに対してAPIキーを発行する
- キーがあれば誰でも認証を通過できる

**アクセストークン認証**
- ユーザーは認可を行うサーバに対し、アクセストークンの発行を要求する
- そこで発行されたアクセストークンを含めて、リクエストを送信する

https://qiita.com/TakahikoKawasaki/items/e37caf50776e00e733be

## バージョニング

貸出の予約や返却期限の設定など新しい機能が必要になった際、既存のクライアントがAPIの変更によって予期しない振る舞いを避けなければなりません。

例えばAPIの応答形式を変更したとき、それを知らないクライアントについても正しい動作を継続させるためにはバージョン管理が必要です。

- URIのパスにバージョン情報を含める

```:例
http://localhost:5000/v2/books
```

## テスト

ここまで実装したWebAPIが、本当に正しく機能するかどうか確かめる必要があります。

**コントラクトテスト**
- エンドポイントとの接続が有効であるか

**コンポーネントテスト**
- アプリケーションの各部分が単独で正しく機能するか
    - 書籍情報が正しく更新されるかをテスト

**シナリオテスト**
- 特定のユーザーストーリーやワークフローを模倣して、アプリケーションが期待どおりに動作するか
    - 書籍の登録から検索まで一連の流れをテスト


**パフォーマンステスト**
- システムのレスポンスタイムやスループット、リソースの使用率など性能を評価
    - 多数のユーザーが同時に書籍の登録や検索を行った場合に、スムーズに動作するかテスト

**セキュリティテスト**
- システムの脆弱性を特定し、不正アクセスやデータ漏洩などのリスクを評価
    - 認証せずにリソースにアクセスした場合の動作をテスト


また**Swagger Test Templates**など、OASファイルからテストケースを自動生成できるツールも複数あります。

## サポートツール

最後にAPIの設計にあたって、様々な場面で活躍する代表的なツールを紹介します。

### Postman

APIの開発、テスト、ドキュメンテーション作成をサポートするツールです。視覚的なインターフェースでAPIリクエストを送信し、レスポンスを確認することができる。チーム単位でのコラボレーション機能やモックサーバーの提供も特徴です。

> モックサーバ：開発やテスト用の仮のサーバ

https://www.postman.com/

### OpenAPI（Swagger）

RESTful APIの仕様を定義・ドキュメント化するための規格です。この仕様を基に、APIのドキュメンテーションやクライアント・サーバーのコードを自動生成するツールが提供されています。視覚的なエディタやAPIテストツールと連携し、開発の効率化をサポートします。

https://www.openapis.org/

### Kong

API通信を管理するためのオープンソースのゲートウェイです。複数のプラグインを通じて認証、トラフィック制御、セキュリティ、モニタリングなどの機能を提供します。大規模な利用や機能の追加にも対応しやすく、最新のサービス構築方法に合っっています。

https://konghq.com/

## まとめ

簡単なWebAPIの設計を通して、基本的な概念とその具体的なイメージについてお伝え出来たと思います。

もちろん、これは入門的な内容に過ぎず、API設計の世界にはさまざまな技術や考え方が存在します。新しい機能の追加や、より独自性を持たせるためのシステムには、多くの試行錯誤を伴うはずです。

本記事がWebAPIの開発を学ぶ初歩としての指南書となり、今後の学習のロードマップとして参考にしていただければ幸いです。

:::note
弊社Nucoでは、他にも様々なお役立ち記事を公開しています。よかったら、Organizationのページも覗いてみてください。
また、Nucoでは一緒に働く仲間も募集しています！興味をお持ちいただける方は、[こちら](https://www.recruit.nuco.co.jp/?qiita_item_id=7dab01ac2ea08b85fb15)まで。
:::
https://www.recruit.nuco.co.jp/?qiita_item_id=7dab01ac2ea08b85fb15

## 設計に関する参考記事

https://qiita.com/KNR109/items/d3b6aa8803c62238d990

https://qiita.com/ryo88c/items/0a3c7861015861026e00