api-design-principles について
api-design-principlesは、RESTとGraphQLのAPIを設計・レビューするためのスキルです。リソースやスキーマの設計、バージョン管理、ページネーション、エラー処理に加え、保守性と開発者向けの実践を扱います。
主な機能
- 01RESTのリソースとHTTPメソッド設計
- 02GraphQLのスキーマとクエリパターン
- 03APIのバージョン管理戦略
- 04ページネーションとエラー処理
- 05DataLoaderとAPIドキュメントの実践
活用シーン
- 01新しいRESTまたはGraphQL APIの設計
- 02API仕様のレビュー
- 03エンドポイントやスキーマの改修
- 04チーム内のAPI規約の策定
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ secondsky/ claude-skills --skill api-design-principlesインストール後、Claude Code で/api-design-principlesを入力して読み込まれたか確認します。
詳細
API 設計の原則
REST と GraphQL の API 設計原則を習得し、直感的で、スケーラブルかつ保守しやすく、開発者に喜ばれ、長く使い続けられる API を構築しましょう。
このスキルを使う場面
- 新しい REST または GraphQL API を設計するとき
- 使いやすさを高めるために既存の API をリファクタリングするとき
- チームの API 設計標準を確立するとき
- 実装前に API 仕様をレビューするとき
- API パラダイム間を移行するとき(REST から GraphQL など)
- 開発者にとって使いやすい API ドキュメントを作成するとき
- 特定のユースケース(モバイル、サードパーティとの連携など)に合わせて API を最適化するとき
コアコンセプト
1. RESTful 設計の原則
リソース指向アーキテクチャ
- リソースは動詞ではなく名詞です(ユーザー、注文、製品など)
- アクションには HTTP メソッドを使用します(GET、POST、PUT、PATCH、DELETE)
- URL はリソースの階層を表します
- 一貫した命名規則を使用します
HTTP メソッドのセマンティクス:
GET:リソースを取得します(冪等かつ安全)POST:新しいリソースを作成しますPUT:リソース全体を置き換えます(冪等)PATCH:リソースを部分的に更新しますDELETE:リソースを削除します(冪等)
2. GraphQL 設計の原則
スキーマファースト開発
- 型でドメインモデルを定義します
- クエリでデータを読み取ります
- ミューテーションでデータを変更します
- サブスクリプションでリアルタイム更新を行います
クエリ構造:
- クライアントは必要なデータだけを正確にリクエストします
- 単一のエンドポイントで複数の操作を実行できます
- 厳密に型付けされたスキーマを使用します
- イントロスペクションが組み込まれています
3. API のバージョニング戦略
URL バージョニング:
/api/v1/users
/api/v2/users
ヘッダーバージョニング:
Accept: application/vnd.api+json; version=1
クエリパラメータバージョニング:
/api/users?version=1
REST API 設計パターン
パターン 1:リソースコレクションの設計
# Good: Resource-oriented endpoints
GET /api/users # List users (with pagination)
POST /api/users # Create user
GET /api/users/{id} # Get specific user
PUT /api/users/{id} # Replace user
PATCH /api/users/{id} # Update user fields
DELETE /api/users/{id} # Delete user
# Nested resources
GET /api/users/{id}/orders # Get user's orders
POST /api/users/{id}/orders # Create order for user
# Bad: Action-oriented endpoints (avoid)
POST /api/createUser
POST /api/getUserById
POST /api/deleteUser
パターン 2:ページネーションとフィルタリング
from fastapi import FastAPI, Query
from pydantic import BaseModel, Field
from typing import List, Optional
class PaginatedResponse(BaseModel):
items: List[dict]
total: int
page: int
page_size: int
pages: int
@property
def has_next(self) -> bool:
return self.page < self.pages
app = FastAPI()
@app.get("/api/users", response_model=PaginatedResponse)
async def list_users(
page: int = Query(1, ge=1),
page_size: int = Query(20, ge=1, le=100),
status: Optional[str] = Query(None),
search: Optional[str] = Query(None)
):
query = build_query(status=status, search=search)
total = await count_users(query)
offset = (page - 1) * page_size
users = await fetch_users(query, limit=page_size, offset=offset)
return PaginatedResponse(
items=users,
total=total,
page=page,
page_size=page_size,
pages=(total + page_size - 1) // page_size
)
パターン 3:エラー処理とステータスコード
from fastapi import HTTPException, status
STATUS_CODES = {
"success": 200,
"created": 201,
"no_content": 204,
"bad_request": 400,
"unauthorized": 401,
"forbidden": 403,
"not_found": 404,
"conflict": 409,
"unprocessable": 422,
"internal_error": 500
}
def raise_not_found(resource: str, id: str):
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail={
"error": "NotFound",
"message": f"{resource} not found",
"details": {"id": id}
}
)
パターン 4:HATEOAS(アプリケーション状態のエンジンとしてのハイパーメディア)
class UserResponse(BaseModel):
id: str
name: str
email: str
_links: dict
@classmethod
def from_user(cls, user: User, base_url: str):
return cls(
id=user.id,
name=user.name,
email=user.email,
_links={
"self": {"href": f"{base_url}/api/users/{user.id}"},
"orders": {"href": f"{base_url}/api/users/{user.id}/orders"},
"update": {"href": f"{base_url}/api/users/{user.id}", "method": "PATCH"},
"delete": {"href": f"{base_url}/api/users/{user.id}", "method": "DELETE"}
}
)
GraphQL 設計パターン
パターン 1:スキーマ設計
type User {
id: ID!
email: String!
name: String!
orders(first: Int = 20, after: String): OrderConnection!
}
type OrderConnection {
edges: [OrderEdge!]!
pageInfo: PageInfo!
totalCount: Int!
}
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String
endCursor: String
}
type Mutation {
createUser(input: CreateUserInput!): CreateUserPayload!
}
input CreateUserInput {
email: String!
name: String!
password: String!
}
type CreateUserPayload {
user: User
errors: [Error!]
}
パターン 2:DataLoader(N+1 問題の防止)
from aiodataloader import DataLoader
class UserLoader(DataLoader):
async def batch_load_fn(self, user_ids: List[str]) -> List[Optional[dict]]:
users = await fetch_users_by_ids(user_ids)
user_map = {user["id"]: user for user in users}
return [user_map.get(user_id) for user_id in user_ids]
ベストプラクティス
REST API
- 一貫した命名:コレクションには複数形の名詞を使用します
- ステートレス:各リクエストに必要な情報をすべて含めます
- HTTP ステータスコードを正しく使う:2xx は成功、4xx はクライアントエラー、5xx はサーバーエラーです
- API にバージョンを付ける:初日から破壊的変更に備えて計画します
- ページネーション:大規模なコレクションには必ずページネーションを適用します
- レート制限:レート制限で API を保護します
- ドキュメント:インタラクティブなドキュメントには OpenAPI/Swagger を使用します
GraphQL API
- スキーマファースト:リゾルバーを書く前にスキーマを設計します
- N+1 問題の回避:効率よくデータを取得するために DataLoader を使用します
- 入力値の検証:スキーマとリゾルバーの両方のレベルで検証します
- エラー処理:ミューテーションのペイロードに構造化されたエラーを返します
- ページネーション:カーソルベースのページネーション(Relay 仕様)を使用します
- 非推奨化:段階的な移行には
@deprecatedディレクティブを使用します - モニタリング:クエリの複雑度と実行時間を追跡します
よくある落とし穴
- 過剰取得/取得不足(REST):GraphQL では解消できますが、DataLoader が必要です
- 破壊的変更:API にバージョンを付けるか、非推奨化の戦略を使用します
- 一貫性のないエラー形式:エラーレスポンスを標準化します
- レート制限の欠如:制限のない API は不正利用に対して脆弱です
- 不十分なドキュメント:ドキュメントのない API は開発者を困らせます
- HTTP セマンティクスの無視:冪等な操作に POST を使うと、期待に反します
- 密結合:API の構造をデータベーススキーマに合わせるべきではありません
コメント
Markdown 対応