本記事は
年度末の振り返りウィーク
4日目の記事です。
🌸
3日目
▶▶ 本記事 ▶▶
5日目
📅

はじめに
こんにちは、入社 2 年目の大澤です。
普段はアプリケーションエンジニアとして開発業務をメインに行っています。
今年度は CI/CD パイプラインの構築、 REST API 設計、フロントエンド・バックエンド開発など多くのことを経験させていただきました。
中でも API 設計については初めての設計ということで学びの多い業務だったと思います。
そこで今回は REST API 設計を経験したことのある方にとっては当たり前の話かとは思いますが、完全初心者が API を設計してみて気づけなかった考え方や意識することをまとめてみようと思います。
1. API のレスポンスに利用者に不要な値は含めない
まずは API レスポンスについてです。
API を設計する際にはレスポンスについても考慮する必要があります。
例として、ユーザアカウントの情報を格納する users テーブルがあるとします。
users テーブル
| user_id | name | created_date | deleted_date | role | |
|---|---|---|---|---|---|
| 1 | 野村太郎 | nomura@example.com | 2026-03-01 | null | USER |
| 2 | 山田太郎 | yamada@example.com | 2026-01-26 | 2026-03-02 | USER |
| 3 | admin | admin@example.com | 2026-01-09 | null | ADMIN |
user_id:ユーザ ID
name:ユーザ名
email:ユーザのメールアドレス
created_date:アカウント作成日
deleted_date:アカウント削除日(論理削除)
role:ユーザ権限
そして、この users テーブルからある一般ユーザのアカウント情報を取得する API のレスポンスを考えてみましょう。
1つの案として以下のようなレスポンスはいかがでしょうか?
// ユーザ情報を取得する API // GET /api/v1/users/{id} { "id": 1, "name": "野村太郎", "email": "nomura@example.com", "created_date": "2026-03-01", "deleted_date": null, "role": "USER" }
実はこのレスポンスにはいくつか問題点があります。
deleted_date という一般ユーザには不要な情報が含まれている
role という管理者以外には閲覧させたくない情報が含まれている
私自身もこのような不要な情報を含んだレスポンスで API を設計してしまい、レビューを通じて「API 利用者が本当に必要な値だけを返せているか」という視点が抜けていることに気付かされました。
この API を一般ユーザ向けと管理者向けで使用するのであれば、エンドポイントごとに責務を分割することも一つの方法です。
今回のユーザ情報取得の API を一般ユーザ向けと管理者向けで分割するのであれば以下のように分けられるでしょう。
// 一般ユーザ向け // GET /api/v1/users/{id} { "id": 1, "name": "野村太郎", "email": "nomura@example.com", "created_date": "2026-03-01" } // 管理者向け // GET /api/v1/admin/users/{id} { "id": 1, "name": "野村太郎", "email": "nomura@example.com", "created_date": "2026-03-01", "deleted_date": null, "role": "USER" }
APIのレスポンスには「必要最小限の情報だけを返す」という原則があります。
不要な情報を返してしまうと、セキュリティリスクや、API 利用者が不要なフィールドに依存してしまうことで将来的な仕様変更の影響範囲が大きくなります。
APIの責務を明確に分割することで、セキュリティリスクを減らし、API 利用者にとっても使いやすいAPIを設計できます。
2. API のパスはリソースの階層構造を意識して設計する
次は API のパスの設計についてです。
先ほどのように本の情報を取得する API を考えます。
あるカテゴリの本一覧を取得する API のパスを考えてみましょう。
リソースをベースに順を追って考えると自然に決まっていきます。
まずはカテゴリをベースに本を取得するのでカテゴリをパスに含めます。
/api/v1/categories
そして、本一覧をカテゴリで絞り込むため、カテゴリ ID をパスパラメータとして含めます。
/api/v1/categories/{category_id}
最後に、本一覧を取得するので複数形の books という単語を含めます。
/api/v1/categories/{category_id}/books
このようにリソースを階層的に考えて API のパスを設計することが大切です。 以下のようにパスがバラバラになると API の拡張性が大きく損なわれます。
/api/v1/categories/books/{category_id}
このパスでは実際に ID が入った時に category_id がどのリソースに対する ID なのかが曖昧になり、パスを見ただけではリソースの親子関係が読み取れません。
// 1 が何に対する番号なのかわかりにくい /api/v1/categories/books/1
私自身、リソースの親子関係などを考慮せずに API を設計してしまい、API 利用者にとって使いづらい API となっていました。
また、階層が深くなりすぎることにも注意が必要です。以下のように階層が 深くなるとパスが読みにくくなります。
特定の本に対するレビューコメントを取得する API パス
/api/v1/categories/{category_id}/books/{book_id}/reviews/{review_id}/comments
パスの階層が深くなる場合はリソース設計自体を見直すことを検討しましょう。
パスが長くなってしまう場合、ID などはクエリパラメータを使用して指定することを検討するのも1つの方法かと思います。
たとえば、上記の階層が深い API パスは以下のように短くすることができるかと思います。
/api/v1/books/{book_id}/reviews/{review_id}/comments
特定の本のレビューコメントを取得するときにカテゴリを指定する必要はないためカテゴリ関連のパスは削除可能となります。
このように、リソースベースで階層を意識した設計にすることで、使いやすい API になります。
3. レスポンスタイムを意識して設計・実装する
最後は API の内部的な処理についての話です。
API を実行するとリクエストを送ってからレスポンスを受け取るまでの時間が発生します。
画面を表示するタイミングで API を実行する場合、レスポンスが遅いとユーザの体験が損なわれてしまいます。
ですので、API を設計する際にはパスやレスポンスの内容だけでなく、応答時間についても考慮をしなければなりません。
例として、本の一覧を取得する API を考えてみます。
前提として以下のように本の情報を管理するテーブルが分かれているとします。
book テーブル
| book_id | title | published_date | isbn | category_id |
|---|---|---|---|---|
| 1 | Java 入門 | 2026-03-01 | 123-4-567-11111-1 | 1 |
| 2 | オブジェクト指向プログラミング | 2025-02-21 | 123-4-567-22222-2 | 1 |
| 3 | API 設計入門 | 2022-11-01 | 123-4-567-33333-3 | 2 |
category テーブル
| category_id | category_name |
|---|---|
| 1 | プログラミング |
| 2 | 設計 |
このようなテーブル構造で以下のようなレスポンスを受け取るとします。
{ "books": [ { "book_id": 1, "title": "Java 入門", "category_name": "プログラミング", "published_date": "2026-03-01", "isbn": "123-4-567-11111-1" }, { "book_id": 2, "title": "オブジェクト指向プログラミング", "category_name": "プログラミング", "published_date": "2025-02-21", "isbn": "123-4-567-22222-2" }, { "book_id": 3, "title": "API 設計入門", "category_name": "設計", "published_date": "2022-11-01", "isbn": "123-4-567-33333-3" } ] }
レスポンスとしては、book テーブルに格納されている category_id をもとに category テーブルからカテゴリ名を取得して格納しています。
この場合どのように DB から情報を取得すればよいでしょうか?
案として以下のような流れが思いつくかもしれません。
SELECT 文で book テーブルから本一覧を取得
各本ごとに category_id から category_name を取得
しかし、このような手順で取得するのは適切ではありません。
このときに意識することは SQL 文の実行回数です。
book テーブルから本一覧の取得で 1 回、そして本の数を N 冊とおくと、各本ごとにカテゴリ名を category テーブルから取得する時の計 1 + N 回です。今回の例でいうと 1 + 3 の計 4 回となります。
例のように book テーブル の中身が少数であれば問題ないのですが、数百、数千件の本が格納されている場合は膨大な SQL 文の実行数になりレスポンスが遅くなってしまいます。
このような問題は「N+1問題」と呼ばれます。
私自身、データ数が少ないから大丈夫だと思い既存の SQL 文をそのまま流用して情報を取得するような構成にしており、将来のデータ増加を考慮できていませんでした。この点はレビューで指摘されて気づきました。
このように、SQL 文の実行回数が N + 1 回になってしまう場合は JOIN 句を使用してテーブルを結合するなど可能な限り SQL 文の実行回数を減らせるように意識しましょう。
これは SQL 文の実行だけでなく、その他外部の API などを使用して情報を取得する時も同じように実行回数の意識が必要です。
リクエストの回数を考慮してレスポンス時間を短縮することでユーザ体験を損なわずに済みます。
おわりに
今回は初めての API 設計でレビューを通じて学んだことをまとめました。
設計の正解は一つではありませんが、「API 利用者の視点」を意識するだけで大きく変わると感じています。
同じく API 設計に挑戦中の方の参考になれば幸いです。