API 設計の18の黄金ルール

[![表紙](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/5c7591df-646f-2bf9-ef14-e7d3217a2dd3.png)](https://leapcell.io/?lc_t=q_api18)

## 1. 署名（シグネチャ）

API インターフェース内のデータの改ざんを防ぐため、多くの場合、API インターフェースには署名（シグネチャ）を適用する必要があります。

リクエストを送信する側は、リクエストパラメータ + タイムスタンプ + 秘密鍵を連結して 1 つの文字列を作成し、それを md5 などのハッシュアルゴリズムで処理し、署名（sign）を生成します。

次に、リクエストパラメータまたはリクエストヘッダーに sign パラメータを追加し、それを API インターフェースに送信します。

API インターフェースのゲートウェイサービスは、受信した sign 値を取得し、同じリクエストパラメータ + タイムスタンプ + 秘密鍵を連結して別の sign を生成し、両者を比較します。

もし 2 つの sign が一致すれば、有効なリクエストと見なし、API インターフェースのゲートウェイサービスはリクエストを該当する業務システムに転送します。

もし 2 つの sign が一致しなければ、API インターフェースのゲートウェイサービスは「署名エラー」を即座に返します。

### 問題：なぜ署名にタイムスタンプを加える必要があるのか？

答え：セキュリティを確保し、同じリクエストが繰り返し利用されることを防ぎ、秘密鍵の解読リスクを軽減するためです。そのため、各リクエストに対して適切な有効期限を設定する必要があります。例えば 15 分などです。

このようにすれば、1 回のリクエストは 15 分以内であれば有効ですが、それを超えると API インターフェースのゲートウェイサービスは「有効期限超過」のエラーメッセージを返します。

現在、署名に使用される秘密鍵には 2 つの形式があります：

1. 双方が固定値の `privateKey` を事前に合意する方法。
2. API インターフェース提供者が `AK/SK` の 2 つの値を提供し、SK を署名の秘密鍵として使用する方法。リクエスト側は AK を `accessKey` としてヘッダーに含めて API インターフェースに送信し、API 側が AK から SK を取得し、新しい署名（sign）を生成します。

## 2. 暗号化

API インターフェースでは、ユーザーのログインパスワード、銀行カード番号、送金金額などの重要なデータを直接送信することがあります。もしこれらの情報が平文のままインターネット上に公開されると非常に危険です。

そのため、データの暗号化が必要です。

例えば、ユーザー登録 API では、ユーザーが入力したパスワードを暗号化する必要があります。

この場合、**AES 対称暗号化アルゴリズム**を利用できます。

- フロントエンドで公開鍵を使ってユーザーのパスワードを暗号化する。
- サーバー側で秘密鍵を使って復号し、必要なバリデーションを実施する。
- その後、別の暗号化方式に変換し、データベースに保存する。

## 3. IP ホワイトリスト

API インターフェースのセキュリティを強化し、署名や暗号化が破られた場合に備えるため、リクエスト元の IP を制限し、**IP ホワイトリスト**を設定することが重要です。

ホワイトリストに登録された IP アドレスからのリクエストのみを許可し、それ以外のアクセスは拒否します。

このホワイトリストは、API ゲートウェイサービス側で管理することも可能です。

ただし、企業の内部アプリケーションサーバーが侵害された場合、それを利用して API インターフェースが不正に呼び出される可能性があります。

このような状況を防ぐため、**Web ファイアウォール（WAF）** を導入することが推奨されます。例えば、`ModSecurity` などのツールを使用するとよいでしょう。

## 4. レートリミット（リクエスト制限）

サードパーティのプラットフォームがあなたの API インターフェースを使用する場合、その呼び出し頻度を制御することは困難です。

もし API へのリクエストが短時間で急増すると、API サービスの可用性が損なわれ、サービスがダウンする可能性があります。

この問題を防ぐために、API インターフェースには**レートリミット（リクエスト制限）**を適用する必要があります。

レートリミットには、以下の 3 つの方法があります：

1. **IP ベースのリクエスト制限**：

   - 例えば、同一 IP からの API インターフェースへのリクエスト数を 1 分間に 10,000 回までに制限する。

2. **特定の API へのリクエスト制限**：

   - 例えば、同一 IP が 1 分間に特定の API インターフェースを呼び出せる回数を 2,000 回までに制限する。

3. **ユーザー単位のリクエスト制限**：
   - 例えば、同じ `AK/SK` を持つユーザーの API インターフェースへのリクエストを 1 分間に 10,000 回までに制限する。

実際の業務では、Nginx、Redis、または API Gateway を使用してレートリミット機能を実装できます。

## 5. パラメータ検証

API インターフェースでは、リクエストのパラメータを適切に検証する必要があります。

例えば：

- 必須フィールドが空でないかチェック
- フィールドのデータ型チェック
- フィールドの長さチェック
- 指定された列挙値（enum）の検証

これにより、無効なリクエストを事前にフィルタリングできます。

例えば、新規データを追加する際に、フィールドの長さがデータベースの最大長を超えると、データベースがエラーを返します。

しかし、このようなエラーをデータベースまで到達させるのではなく、API インターフェースで事前に検知し、システムリソースの無駄を防ぐべきです。

他にも、数値フィールドの制限（例えば、金額が負の値でないことの確認）や、存在しない列挙値の入力を防ぐことも重要です。

Java の場合、データ検証には `Hibernate Validator` フレームワークが最もよく使用されます。これには以下のようなアノテーションが含まれています：

- `@Null`
- `@NotEmpty`
- `@Size`
- `@Max`
- `@Min`

これらを使用すれば、データの検証が容易になります。

また、日付フィールドや列挙型フィールドについては、カスタムアノテーションを作成してバリデーションを実施することも可能です。

## 6. 統一されたレスポンスフォーマット

過去に他社の API を利用した際、正常なレスポンスのフォーマットは JSON 形式で統一されていました。

例えば：

```json
{
  "code": 0,
  "message": null,
  "data": [{ "id": 123, "name": "abc" }]
}
```

しかし、署名エラー時のレスポンスは以下のように異なっていました：

```json
{
  "code": 1001,
  "message": "署名エラー",
  "data": null
}
```

さらに、アクセス権限がない場合のレスポンスは：

```json
{
  "rt": 10,
  "errorMgt": "権限がありません",
  "result": null
}
```

このように、**API の異常時に異なるフォーマットが使用されると、API を利用する側にとって理解しにくくなります。**

この問題の原因は、API ゲートウェイと業務システムのレスポンス構造が異なっていることにあります。

- ゲートウェイで発生したエラー → ゲートウェイのレスポンス形式
- 業務システムで発生したエラー → 業務システムのレスポンス形式

このような設計では、API が異常を返す際に**異なるレスポンスフォーマット**となり、メンテナンスが非常に困難になります。

この問題は、**API ゲートウェイ側で統一フォーマットに変換**することで解決できます。

例えば、業務システムで例外が発生した場合は `RuntimeException` をスローし、その `message` フィールドにエラー内容を設定します。

API ゲートウェイはこの例外をキャッチし、統一されたレスポンス形式でクライアントに返します。

## 7. 例外の統一処理

API インターフェースでは、例外処理を統一する必要があります。

例えば、データベースにアクセスする API で、テーブルが存在しない場合や SQL 文にエラーがある場合、直接 SQL エラーメッセージが API レスポンスに含まれるケースがあります。

このようなレスポンスには、以下のような情報が含まれることがあります：

- 例外スタックトレース
- データベース情報
- SQL エラーコード
- エラー発生行数

これらの情報を第三者に公開すると、非常に危険です。

**悪意のあるユーザーは、これらの情報を利用して SQL インジェクション攻撃を仕掛けたり、データベースの内容を漏洩させたりする可能性があります。**

そのため、API では例外を統一的に処理し、安全なレスポンスフォーマットに変換することが重要です。

例えば、エラー発生時には次のようなレスポンスを返します：

```json
{
  "code": 500,
  "message": "サーバー内部エラー",
  "data": null
}
```

- `code`: `500`（内部サーバーエラー）
- `message`: `"サーバー内部エラー"`

このように処理することで、**API の内部エラーであることはクライアントに通知できますが、詳細なエラー情報は秘匿されます。**

しかし、開発者がデバッグしやすいように、サーバーのログには詳細なエラースタックや SQL 情報を記録しておくことが重要です。

この例外処理は、API ゲートウェイで統一的に処理し、外部には**機密情報を含まないエラーメッセージ**を提供することが推奨されます。

## 8. リクエストログの記録

第三者のプラットフォームが API を呼び出す際、**リクエストログは非常に重要**です。

ログを分析することで、問題の特定やデバッグが容易になります。

記録すべき情報は以下の通りです：

- API のリクエスト URL
- リクエストパラメータ
- リクエストヘッダー
- リクエストメソッド（GET, POST など）
- レスポンスデータ
- レスポンス時間

さらに、**traceId（トレース ID）** を導入すると、ログを一連のリクエスト単位で管理でき、不要なログを除外することができます。

また、リクエストログは開発者だけでなく、第三者プラットフォームのユーザーにも必要になることがあります。

この場合、**MongoDB や Elasticsearch などのデータベースにログを保存し、UI を用意することで外部のユーザーがログを確認できるようにする**のが望ましいです。

## 9. 冪等性の設計

サードパーティのプラットフォームが、極めて短い時間間隔で API を複数回リクエストするケースは珍しくありません。

例えば：

- 1 秒以内に同じリクエストが 2 回送信される
- サードパーティ側のバグや、リクエスト失敗時のリトライ機構が影響する

この場合、**API の冪等性を設計しておかないと、同じデータが重複して作成される可能性があります。**

つまり、サードパーティが**同じリクエストを複数回送信しても、1 回目はデータを作成し、2 回目以降は処理をスキップしながら成功を返す**ようにする必要があります。

冪等性を確保する方法として、以下の方法が考えられます：

1. **データベースにユニークインデックスを追加する**

   - 例えば、注文 ID (`orderId`) にユニークインデックスを設定し、同じ `orderId` のデータが 2 回作成されるのを防ぐ。

2. **Redis に `requestId` を保存してチェック**
   - API リクエスト時に `requestId` を生成し、それを Redis に保存。
   - 同じ `requestId` を持つリクエストが再送信された場合は、2 回目以降の処理をスキップ。

このようにすることで、**同じリクエストが短時間で複数回送信されても、API は意図しないデータの重複作成を防ぐことができます。**

## 10. レコード数の制限

バッチ処理が可能な API を提供する場合、**1 回のリクエストで処理するレコード数に制限を設けることが重要**です。

もしリクエストで送信されるデータ量が多すぎると、以下の問題が発生する可能性があります：

- API の処理時間が長くなり、**タイムアウトが発生する**
- **API サーバーの負荷が増大し、不安定になる**
- **メモリや CPU を圧迫し、サービスのダウンにつながる**

この問題を防ぐために、以下のような制限を設けるのが一般的です：

- **1 回のリクエストで送信できるレコード数を 500 件までに制限する**
- **500 件を超える場合はエラーメッセージを返す**
- **この制限値を設定可能なパラメータとして設計し、サードパーティと事前に合意しておく**

また、データ量が多すぎる場合には、**ページネーション（ページ分割）を導入し、分割してデータを取得できるようにする**ことが推奨されます。

## 11. 負荷テスト（圧力テスト）

API を本番環境にリリースする前に、**負荷テスト（ストレステスト）を実施し、各 API の QPS（queries per second）を把握することが重要**です。

**負荷テストの目的：**

- API の**最大処理能力**を事前に把握し、適切なサーバー台数を見積もる
- 設定したレートリミットが**適切に機能しているか検証する**
- 実際のトラフィック増加に対する API の**耐久性を評価する**

例えば：

- **レートリミットが「1 秒間に 50 件」だとしても、API が実際には 30 件しか処理できない可能性がある**
- **この場合、想定より少ないリクエストで API がパンクする可能性があるため、事前に検証が必要**

負荷テストを実施するには、以下のツールを使用できます：

- `JMeter`（Apache JMeter）
- `Apache Bench（ab コマンド）`

これらのツールを使い、**API の耐久性を事前にチェックすることで、サービスダウンを防ぐことができます。**

## 12. 非同期処理

一般的に、API のロジックは**同期的**に処理されます。つまり、リクエストが完了するとすぐにレスポンスを返します。

しかし、**業務ロジックが複雑な場合や、大量のデータを処理するバッチ API では、同期処理では時間がかかりすぎる**ことがあります。

このような場合、API のパフォーマンスを向上させるため、**非同期処理**を導入するのが有効です。

### 非同期処理の方法

1. **メッセージキュー（MQ）を利用する**

   - API を呼び出した際に、**即座に MQ にメッセージを送信**し、成功レスポンスを返す
   - 別の **MQ コンシューマー** がこのメッセージを受信し、非同期で業務ロジックを処理する
   - 例：RabbitMQ、Kafka、Redis Queue などを使用

2. **サードパーティが処理結果を取得する方法**
   - **コールバック方式**：API 側が処理完了後、**サードパーティのエンドポイントへリクエストを送信し、結果を通知**
   - **ポーリング方式**：サードパーティが **定期的に API に問い合わせを行い、処理状況を確認**（ステータス API を用意）

このように、**業務の負荷を非同期で処理することで、API の応答速度を向上させることができます。**

## 13. データのマスキング（匿名化）

API を通じてデータを提供する際、一部の情報は機密情報である可能性があります。

例えば：

- ユーザーの電話番号
- クレジットカード番号
- 住所情報

これらのデータをそのまま外部に公開すると、**個人情報の漏洩リスク**が高まります。

この問題を防ぐために、**データの一部をマスキング（匿名化）する**のが一般的です。

**マスキングの例**

- クレジットカード番号：`5196****1234`
- 電話番号：`090-****-5678`
- メールアドレス：`user***@gmail.com`

このように部分的にデータを伏せることで、**情報漏洩のリスクを軽減**することができます。

## 14. 完全な API ドキュメントの作成

API を提供する際、**明確で詳細なドキュメントを作成することが重要**です。

API ドキュメントが適切に整備されていれば、**開発者同士のコミュニケーションコストを削減し、スムーズな連携が可能**になります。

### API ドキュメントに含めるべき情報

- **エンドポイントの URL**
- **リクエストメソッド（GET / POST など）**
- **リクエストパラメータとその説明**
- **レスポンスフォーマットとフィールドの説明**
- **エラーメッセージの一覧**
- **署名や暗号化の例**
- **リクエストのサンプルコード**
- **IP ホワイトリストの設定方法**

また、**統一された命名規則を適用することも重要**です。

例えば：

- フィールドの命名を **camelCase（小文字 + 大文字）** に統一する
- `id` の型を `Long`（最大 20 桁）と決める
- `status` の型を `int`（固定 2 桁）とする
- 日時フィールドは `yyyy-MM-dd HH:mm:ss` フォーマットを使用する

さらに、API キー（AK/SK）や API のドメイン情報も明記し、適切な権限を持つユーザーのみが確認できるようにすることが重要です。

## 15. リクエスト方式の選択

API では様々なリクエスト方式を選択できます（GET、POST、PUT、DELETE など）。

しかし、実際の業務では **GET と POST が最も一般的**に使用されます。

### GET と POST の使い分け

✅ **GET**

- 入力パラメータを持たない場合
- クエリ文字列を使ってデータを取得する場合

✅ **POST**

- 入力パラメータが必要な場合
- **リクエストパラメータが長い場合（GET には 5000 文字の制限がある）**
- **拡張性を考慮する場合（パラメータが増えても影響を受けにくい）**

そのため、**複雑なデータを扱う API では POST を推奨**します。

## 16. リクエストヘッダーの活用

API 設計において、**すべてのパラメータをリクエストボディやクエリパラメータに含める必要はありません。**  
一部の共通パラメータは **リクエストヘッダー** に設定すると便利です。

### リクエストヘッダーに含めるべき項目

- **認証情報（例：API Key, Access Token）**
- **トレーシング ID（traceId）**
- **クライアント情報（例：User-Agent, Client-ID）**

例えば、`traceId` をリクエストごとに発行し、ヘッダーに含めることで、**API のログを統一的に管理しやすくなります。**

**ヘッダーの例**

```http
POST /api/v1/order HTTP/1.1
Host: api.example.com
Authorization: Bearer xxxxxx
traceId: 123456789abcdef
Content-Type: application/json
```

サーバー側では、**リクエストを一元管理するためのミドルウェアを設け、ヘッダー情報をログに記録すること**が推奨されます。

## 17. バッチ処理の設計

API 設計では、**データの取得、追加、更新、削除の際に、バッチ処理を考慮すること**が重要です。

例えば：

- **注文 ID をもとに注文の詳細を取得する API**

  - **NG:** `GET /api/order?id=123`
  - **OK:** `POST /api/order/details`（リクエストボディで複数 ID を送信可能）

- **データの一括追加**
  - **NG:** 1 つずつリクエストを送信する
  - **OK:** 1 回のリクエストで最大 1000 件のデータを追加可能にする

**単一リクエストに制限せず、できるだけバッチ対応の API を設計することで、無駄な通信を削減できます。**

## 18. 単一責務の原則（SRP: Single Responsibility Principle）

API 設計の際、**「すべてを 1 つのエンドポイントで処理しようとしない」** ことが重要です。

### 悪い例

```http
POST /api/order/process
```

- `orderType`（通常注文 / クイック注文）を判定
- `platformType`（Web / モバイル）を判定
- `paymentMethod`（クレジットカード / PayPal）を処理

このように 1 つの API に **複数の責務を持たせると、コードが複雑化し、変更が困難になります。**

### 良い例

```http
POST /web/v1/order/create
POST /web/v1/order/fastCreate
POST /mobile/v1/order/create
POST /mobile/v1/order/fastCreate
```

- Web とモバイルでエンドポイントを分ける
- 通常注文とクイック注文を明確に分離

**単一責務の原則を守ることで、API の可読性とメンテナンス性が向上します。**

## まとめ

API 設計を行う際に考慮すべき 18 のポイントを紹介しました。

これらのポイントを考慮することで、より安全で効率的な API を設計できます。

---

### [私たちはLeapcell、バックエンド・プロジェクトのホスティングの最適解です。](https://leapcell.io/?lc_t=q_api18)

[![Leapcell](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/7acb9565-2cbf-d929-427a-8c6c67c674d5.png)](https://leapcell.io/?lc_t=q_api18)

[Leapcell](https://leapcell.io/?lc_t=q_api18)は、Webホスティング、非同期タスク、Redis向けの次世代サーバーレスプラットフォームです：

**複数言語サポート**

- Node.js、Python、Go、Rustで開発できます。

**無制限のプロジェクトデプロイ**

- 使用量に応じて料金を支払い、リクエストがなければ料金は発生しません。

**比類のないコスト効率**

- 使用量に応じた支払い、アイドル時間は課金されません。
- 例: $25で6.94Mリクエスト、平均応答時間60ms。

**洗練された開発者体験**

- 直感的なUIで簡単に設定できます。
- 完全自動化されたCI/CDパイプラインとGitOps統合。
- 実行可能なインサイトのためのリアルタイムのメトリクスとログ。

**簡単なスケーラビリティと高パフォーマンス**

- 高い同時実行性を容易に処理するためのオートスケーリング。
- ゼロ運用オーバーヘッド — 構築に集中できます。

[ドキュメントで詳細を確認！](https://docs.leapcell.io/)

[![Try Leapcell](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/eeffc4fa-12e7-47e3-afeb-fa0bde6cce4a.png)](https://leapcell.io/?lc_t=q_api18)

Xでフォローする：[@LeapcellHQ](https://x.com/LeapcellHQ)

---

[ブログでこの記事を読む](https://leapcell.io/blog/bulletproof-api-design)