はじめに:そのAPIのURL、まだ「動詞」で作っていませんか?
ネットワークエンジニアとして数々の現場を渡り歩いてきた私だが、最近のアプリケーション開発現場やクラウド基盤との連携で、思わず頭を抱えたくなるようなWeb APIの設計に遭遇することがある。
例えば、こんなURLだ。
POST /getUserData?id=101
GET /updateUserProfile
DELETE /deletePost
おいおい、ちょっと待ってくれ。ここはパケットの宛先やルーティングを定義する世界であって、リモートプロシージャコール(RPC)の関数名を書く掲示板じゃない。URL(Uniform Resource Locator)という名前の通り、それは「どこにある、何というリソース(資源)」を指し示しているべきなのだ。
ネットワークの世界に目を向けてみてほしい。ルーターのインターフェースを指定するとき、私たちは shutDownInterface なんていうパスは叩かない。/interfaces/eth0 という「名詞」を指定し、それに対する操作(状態変更)はプロトコルが持つ制御コマンドやフラグで行う。Web APIにおけるリソース指向設計(ROA:Resource-Oriented Architecture)も、これとまったく同じ思想に基づいている。
今回は、REST APIの根幹をなす「リソース指向設計における名詞の活用」について、RFCの定めに裏打ちされた仕様と、現場で明日から使える実践的な設計手法を徹底的に解説しよう。
—
1. なぜ「名詞」なのか?RFCとHTTPメソッドの本来の姿
Webの基盤であるHTTP(Hypertext Transfer Protocol)は、元々ドキュメントのハイパーテキストを転送するために設計された。RFC 9110(HTTP Semantics)や、その前身であるRFC 7231が規定するように、HTTPは「リソース」に対して「メソッド(動詞)」を適用するというシンプルかつ強力なセマンティクスを持っている。
エンドポイントURL(URIパス)に動詞を含めてしまうと、何が起こるだろうか?
1. HTTPメソッドの二重定義による矛盾
POST /deleteUser というURLに対して、実際には GET でアクセスされたり、逆に GET /getUser なのに POST でデータを送ったりと、HTTP本来のメソッドが持つ冪等性(Idempotency)や安全性(Safety)のセマンティクスが完全に崩壊する。
2. キャッシュ機構の破綻
プロキシサーバーやCDN(Content Delivery Network)は、URLのパスとHTTPメソッドの組み合わせでキャッシュを最適化する。動詞が混じったカオスなURLは、キャッシュヒット率を著しく低下させ、バックエンドのデータベースへ無駄な負荷を直撃させる原因になる。
操作を表す「動詞」はHTTPメソッド(GET, POST, PUT, PATCH, DELETE)に任せ、URLパスは純粋に「リソース(名詞)」の階層構造を表現する。これがRESTの美しさであり、実運用における堅牢性を生む最大の秘訣なのだ。
—
2. 実務で役立つ!美しいリソース名設計の4大原則
では、具体的にどのように名詞を組み立てていけばよいのだろうか?現場で迷いがちなポイントを4つのルールに落とし込んで解説する。
原則①:コレクションは「複数形の名詞」、個別リソースは「単数形+ID」
リソースの集合(コレクション)を指す場合は複数形を使い、特定の個体を指す場合はその配下に識別子(ID)を配置する。
- ユーザーのリスト:
GET /users - 特定のユーザー詳細:
GET /users/42
ここで、user と単数形にするか users と複数形にするかは宗教論争になりがちだが、業界標準のデファクトスタンダードは「コレクション単位の複数形」である。実務では一貫性が命なので、チーム内で必ずガイドラインを統一してほしい。
原則②:階層関係はスラッシュ(/)で表現する
リソースが別のリソースに従属している場合(親子関係)、パスを階層的に表現することで、関係性がひと目でわかる美しいURLになる。
- 特定ユーザーが所有する投稿一覧:
GET /users/42/posts - 特定ユーザーの特定の投稿詳細:
GET /users/42/posts/1001
この階層構造を見ると、パケットがどのデータベースのテーブルをヒットし、どのようなJOINクエリが走るのかがインフラエンジニアの頭の中でも直感的にイメージできるはずだ。
原則③:どうしても動詞を使いたくなる例外(コントローラーリソース)
「リソースのCRUD(作成・読み取り・更新・削除)には綺麗に当てはまらない、純粋なアクション(例:メール送信、パスワードリセット、複雑なバッチ処理のトリガー)」を実行したい場合はどうすべきか。
このようなケースでは、例外的にURLの末尾に「動詞(正確にはアクションを名詞化したもの、あるいはその機能を表す名詞)」を置く設計が許容される。これをコントローラーリソースと呼ぶ。
- パスワードリセットメールの送信:
POST /users/42/password-reset - アカウントの有効化:
POST /users/42/activation
ポイントは、これもあくまで POST というHTTPメソッドと組み合わせており、URL自体が「リセットという手続き(プロシージャ)という名のオブジェクト」を指していると解釈することだ。
原則④:単語の区切りはケバブケース(kebab-case)
URLパスに含まれる単語が複数になる場合、可読性を高めるためにハイフン(-)で繋ぐケバブケースを使用する。アンダースコア(_)は、フォントやディスプレイ環境によっては下線で見えづらくなることがあるため、URLパスでは避けるのが無難だ。
- 良い例:
GET /user-profiles - 避けるべき例:
GET /user_profiles,GET /userProfiles(キャメルケースはクエリパラメータやJSONのキーには使うが、URIパスには推奨されない)
—
3. シーケンスとデータ構造:RESTfulな通信の裏側
ここで、実際にクライアントからサーバー、そしてデータベースに至るまでの通信フローと、やり取りされるデータの構造を確認しておこう。
[Client (Fetch API)] [API Gateway / Router] [Backend / DB]
| | |
|--- 1. GET /users/42/posts ---------->| |
| (Accept: application/json) |--- 2. SELECT * FROM... ->|
| |<-- 3. Return Rows -------|
|<-- 4. 200 OK (JSON Payload) ---------| |
| | |
クライアントはHTTPメソッドと名詞化されたエンドポイントを指定し、サーバーはそれに応じたリソース表現を返す。この時、ヘッダーでコンテンツタイプを正しくネゴシエーションすることも忘れてはならない。
—
4. 実装コード例:モダンな環境でのリクエスト構築
現場で即座に使える、リソース指向設計に則ったAPIアクセスコードのサンプルをいくつか提示しよう。
Python (requests) によるリソース操作
Pythonの requests ライブラリを使い、ユーザーリソースの取得、新規作成、削除を美しく実装した例だ。
import requests
# APIのベースURL(必ず名詞のコレクションで終わる)
BASE_URL = "https://api.example.com/v1"
headers = {
"Accept": "application/json",
"Content-Type": "application/json"
}
def get_user_posts(user_id: int):
"""特定のユーザーが持つ投稿一覧を取得する (GET)"""
url = f"{BASE_URL}/users/{user_id}/posts"
response = requests.get(url, headers=headers)
if response.status_code == 200:
print("投稿一覧の取得に成功しました:", response.json())
else:
print(f"エラー発生: ステータスコード {response.status_code}")
def create_user_post(user_id: int, title: str, body: str):
"""特定のユーザーに対して新しい投稿を作成する (POST)"""
url = f"{BASE_URL}/users/{user_id}/posts"
payload = {
"title": title,
"body": body
}
response = requests.post(url, headers=headers, json=payload)
if response.status_code == 201: # Created
print("投稿が正常に作成されました:", response.json())
# 実行例
if __name__ == "__main__":
get_user_posts(42)
create_user_post(42, "REST APIの極意", "名詞で設計する美しさについて")
JavaScript (Fetch API) による非同期通信
フロントエンド(ブラウザやNode.js)から標準の fetch を用いて個別リソースを削除(DELETE)する例。
/**
* 指定したIDのユーザーを削除する (DELETE)
* @param {number} userId - 削除対象のユーザーID
*/
async function deleteUser(userId) {
const endpoint = `https://api.example.com/v1/users/${userId}`;
try {
const response = await fetch(endpoint, {
method: 'DELETE',
headers: {
'Accept': 'application/json'
}
});
// 204 No Content は削除成功時の標準的なステータスコード
if (response.status === 204) {
console.log(`ユーザー ID: ${userId} の削除が完了しました。`);
} else if (response.status === 404) {
console.error('対象のユーザーが見つかりませんでした。');
} else {
console.error('予期せぬエラーが発生しました:', response.status);
}
} catch (error) {
console.error('ネットワーク層またはCORSエラー:', error);
}
}
// 実行
deleteUser(42);
—
5. 現場のトラブルシューティングとTips
最後に、私がインフラ構築やAPIの負荷分散・ログ解析の現場で直面した、リソース設計にまつわる「生々しい教訓」をいくつかシェアしよう。
- Tip 1: クエリパラメータとパスパラメータの境界線を見誤るな
「絞り込み条件」や「ソート順、ページネーション」は、リソースそのものの識別子ではないため、URIパスに含めず、必ずクエリパラメータ(?status=active&sort=-created_at)として逃がすこと。パスはあくまで「ツリー構造の静的な位置」を表すために使え。
- Tip 2: リバースプロキシ(NginxやEnvoy)でのルーティング設計が劇的に楽になる
URLが /users/{id}/posts のように綺麗に名詞とIDの階層で整理されていると、API Gatewayやリバースプロキシのルーティングルール(正規表現やパスプレフィックスマッチ)が非常にシンプルになる。逆に動詞が混じったバラバラのURLだと、プロキシ側で個別のルーティング定義が数珠繋ぎになり、保守地獄に陥る。
- Tip 3: 複数形か単語の揺れにチームで怯えないための「API仕様書ファースト」
開発の途中で「やっぱりこの名前は単数形だっけ?複数形だっけ?」と揉めるのは時間の無駄だ。実装を始める前に、OpenAPI(Swagger)などのツールを使ってエンドポイントのURL設計図を完全に固め、チーム全員で合意形成を取ることを強く推奨する。
—
おわりに
URLを「名詞」で構成し、振る舞いを「HTTPメソッド」に委ねる。このリソース指向設計(ROA)の原則は、単なるお上品なルールではなく、ネットワークの可用性、キャッシュ効率、コードの保守性、そしてシステム全体の美しさを担保するための「エンジニアの知恵」そのものである。
次に新しいAPIのエンドポイントを設計するとき、あるいは既存の汚れたURLをリファクタリングするときは、ぜひこの記事の原則を思い出してほしい。パケットが流れるその道筋が美しく整ったとき、あなたのシステムはより強靭で、愛されるインフラへと進化しているはずだ。
コメント