mcpField
A
api-design

api-design

github.com/miles990開発2665 回インストール41 週間前に更新
リポジトリ

api-design について

REST、GraphQL、gRPC、リアルタイムプロトコルを扱う、API開発者向けの設計ガイドです。バージョン管理、セキュリティ、ドキュメント、設計上の注意点も取り上げます。

主な機能

  1. 01RESTのリソース命名とHTTPメソッド
  2. 02GraphQLのスキーマとリゾルバー設計
  3. 03gRPCとProtocol Buffers
  4. 04APIのバージョン管理と非推奨化
  5. 05リアルタイムAPIとセキュリティ対策
  6. 06OpenAPIドキュメントと設計チェック

活用シーン

  1. 01新しいAPIのパスやレスポンス形式の検討
  2. 02APIのバージョン変更時の互換性評価
  3. 03リアルタイム通信に使うプロトコルの選定
  4. 04入力検証やレート制限のリスク確認

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/miles990/claude-software-skills --skill api-design

インストール後、Claude Code で/api-designを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

templates/
openapi.yaml
README.md
schema.graphql

詳細

API 設計

概要

直感的で一貫性があり、スケーラブルな API を構築するための設計原則です。REST、GraphQL、gRPC、リアルタイムプロトコルを扱います。


RESTful API 設計

リソースの命名
✅ Good (nouns, plural):
GET    /users           # List users
GET    /users/123       # Get user
POST   /users           # Create user
PUT    /users/123       # Update user
DELETE /users/123       # Delete user

❌ Bad (verbs, actions):
GET    /getUsers
POST   /createUser
POST   /users/123/delete
ネストされたリソース
# Hierarchical relationship
GET /users/123/orders              # User's orders
GET /users/123/orders/456          # Specific order

# Alternative: Query parameter for filtering
GET /orders?userId=123             # Filter orders by user

# Rule: Nest max 2 levels deep
❌ /users/123/orders/456/items/789/reviews
✅ /order-items/789/reviews
HTTP メソッドとステータスコード
メソッド目的成功エラー
GET読み取り200404
POST作成201400, 409
PUT置換200400, 404
PATCH部分更新200400, 404
DELETE削除204404
// Response structure
interface ApiResponse<T> {
  data: T;
  meta?: {
    page: number;
    limit: number;
    total: number;
  };
}

interface ApiError {
  error: {
    code: string;        // Machine-readable
    message: string;     // Human-readable
    details?: object;    // Validation errors, etc.
  };
}
ページネーション
// Offset-based (simple, has issues with large datasets)
GET /users?page=2&limit=20

// Cursor-based (stable, performant)
GET /users?cursor=eyJpZCI6MTIzfQ&limit=20

// Response
{
  "data": [...],
  "meta": {
    "nextCursor": "eyJpZCI6MTQzfQ",
    "hasMore": true
  }
}
フィルタリングとソート
// Query parameters
GET /products?category=electronics&minPrice=100&maxPrice=500
GET /products?sort=-createdAt,name  // - prefix for descending

// Filter operators
GET /users?age[gte]=18&age[lte]=65
GET /users?status[in]=active,pending
GET /users?name[like]=john*

GraphQL 設計

スキーマ設計
type User {
  id: ID!
  email: String!
  name: String!
  posts(first: Int, after: String): PostConnection!
  createdAt: DateTime!
}

type Post {
  id: ID!
  title: String!
  content: String!
  author: User!
  comments: [Comment!]!
}

# Connections for pagination (Relay spec)
type PostConnection {
  edges: [PostEdge!]!
  pageInfo: PageInfo!
}

type PostEdge {
  node: Post!
  cursor: String!
}

type Query {
  user(id: ID!): User
  users(first: Int, after: String, filter: UserFilter): UserConnection!
}

type Mutation {
  createUser(input: CreateUserInput!): CreateUserPayload!
  updateUser(id: ID!, input: UpdateUserInput!): UpdateUserPayload!
}

input CreateUserInput {
  email: String!
  name: String!
  password: String!
}

type CreateUserPayload {
  user: User
  errors: [Error!]
}
リゾルバのパターン
// N+1 prevention with DataLoader
const userLoader = new DataLoader(async (ids: string[]) => {
  const users = await db.users.findMany({ where: { id: { in: ids } } });
  return ids.map(id => users.find(u => u.id === id));
});

const resolvers = {
  Post: {
    author: (post, _, { loaders }) => loaders.user.load(post.authorId),
  },
};

// Field-level authorization
const resolvers = {
  User: {
    email: (user, _, { currentUser }) => {
      if (currentUser.id !== user.id && !currentUser.isAdmin) {
        return null; // Hide from other users
      }
      return user.email;
    },
  },
};
エラーハンドリング
// Union types for expected errors
type CreatePostResult = Post | ValidationError | NotAuthorizedError

// Or use errors field in payload
type CreatePostPayload {
  post: Post
  errors: [CreatePostError!]
}

union CreatePostError = ValidationError | RateLimitError

gRPC 設計

Protocol Buffers
syntax = "proto3";

package users.v1;

service UserService {
  rpc GetUser(GetUserRequest) returns (GetUserResponse);
  rpc ListUsers(ListUsersRequest) returns (ListUsersResponse);
  rpc CreateUser(CreateUserRequest) returns (CreateUserResponse);

  // Streaming
  rpc WatchUsers(WatchUsersRequest) returns (stream UserEvent);
  rpc BatchCreateUsers(stream CreateUserRequest) returns (BatchCreateResponse);
}

message User {
  string id = 1;
  string email = 2;
  string name = 3;
  google.protobuf.Timestamp created_at = 4;
}

message GetUserRequest {
  string id = 1;
}

message GetUserResponse {
  User user = 1;
}

message ListUsersRequest {
  int32 page_size = 1;
  string page_token = 2;
  UserFilter filter = 3;
}

message ListUsersResponse {
  repeated User users = 1;
  string next_page_token = 2;
}
gRPC を使うべき場合
ユースケースRESTGraphQLgRPC
パブリック API✅✅❌
モバイルアプリ✅✅⚠️
マイクロサービス⚠️❌✅
リアルタイム❌⚠️✅
ブラウザクライアント✅✅⚠️

API バージョニング

戦略
# URL versioning (most common)
GET /v1/users
GET /v2/users

# Header versioning
GET /users
Accept: application/vnd.api+json; version=2

# Query parameter
GET /users?version=2
破壊的変更と非破壊的変更
Non-Breaking (safe):
✅ Add new optional field
✅ Add new endpoint
✅ Add new optional query parameter
✅ Expand enum values (if client ignores unknown)

Breaking (requires new version):
❌ Remove field
❌ Rename field
❌ Change field type
❌ Make optional field required
❌ Change URL structure
非推奨化の戦略
// OpenAPI deprecation
/**
 * @deprecated Use /v2/users instead. Will be removed on 2025-06-01.
 */
app.get('/v1/users', ...);

// Response header
res.setHeader('Deprecation', 'true');
res.setHeader('Sunset', 'Sat, 01 Jun 2025 00:00:00 GMT');
res.setHeader('Link', '</v2/users>; rel="successor-version"');

リアルタイム API

WebSocket
// Server
wss.on('connection', (ws) => {
  ws.on('message', (data) => {
    const message = JSON.parse(data);

    switch (message.type) {
      case 'subscribe':
        subscriptions.add(ws, message.channel);
        break;
      case 'unsubscribe':
        subscriptions.remove(ws, message.channel);
        break;
    }
  });
});

// Client
const ws = new WebSocket('wss://api.example.com/ws');

ws.send(JSON.stringify({
  type: 'subscribe',
  channel: 'orders:user:123'
}));

ws.onmessage = (event) => {
  const data = JSON.parse(event.data);
  handleUpdate(data);
};
Server-Sent Events (SSE)
// Server
app.get('/events', (req, res) => {
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');

  const sendEvent = (data: object) => {
    res.write(`data: ${JSON.stringify(data)}\n\n`);
  };

  // Subscribe to events
  eventEmitter.on('update', sendEvent);

  req.on('close', () => {
    eventEmitter.off('update', sendEvent);
  });
});

// Client
const source = new EventSource('/events');
source.onmessage = (event) => {
  const data = JSON.parse(event.data);
  handleUpdate(data);
};

API ドキュメント

OpenAPI (Swagger)
openapi: 3.0.3
info:
  title: User API
  version: 1.0.0

paths:
  /users:
    get:
      summary: List users
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            default: 20
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/User'

components:
  schemas:
    User:
      type: object
      required: [id, email, name]
      properties:
        id:
          type: string
          format: uuid
        email:
          type: string
          format: email
        name:
          type: string

セキュリティのベストプラクティス

プラクティス実装
認証ベアラートークン、API キー
レート制限X-RateLimit-* ヘッダー
入力検証スキーマ検証 (Zod、Joi)
CORS許可するオリジンのホワイトリスト
HTTPS本番環境では常に使用
リクエスト IDトレース用の X-Request-ID
// Rate limit headers
res.setHeader('X-RateLimit-Limit', '100');
res.setHeader('X-RateLimit-Remaining', '95');
res.setHeader('X-RateLimit-Reset', '1640000000');

関連スキル

  • [[architecture-patterns]] - API ゲートウェイ、マイクロサービス
  • [[security-practices]] - 認証、認可
  • [[documentation]] - API ドキュメントツール

Sharp Edges(よくある落とし穴)

これらは API 設計において最もよくある、最もコストの高い間違いです

SE-1: 破壊的変更 (Breaking Changes)
  • 深刻度: critical
  • 状況: 適切なバージョニングなしに既存の API を変更し、クライアントがクラッシュする
  • 原因: フィールドの削除、フィールド型の変更、必須かどうかの変更、名前の変更
  • 症状:
    • クライアントで突然エラーが発生する
    • 旧バージョンのアプリが使用できなくなる
    • ユーザーから「昨日は使えたのに、今日は壊れた」という苦情が来る
  • 検出: \-.*field|rename.*property|required.*true.*→.*false|type.*string.*→.*number
  • 解決策: API バージョニングを使用する、追加的な変更のみを行う、非推奨化の期限を設定する
SE-2: 過剰取得 (Over-fetching)
  • 深刻度: medium
  • 状況: API がクライアントにとって不要なデータを大量に返す
  • 原因: 「とにかく全部返しておけばいい」という姿勢、異なるユースケースを考慮していない
  • 症状:
    • API レスポンスは大きいが、クライアントはそのごく一部しか使わない
    • モバイルデバイスでの読み込みが遅い
    • 帯域幅の無駄
  • 検出: select\s*\*|findMany\(\)|findAll\(\)(?!.*select)
  • 解決策: フィールド選択を使用する、GraphQL を使う、要件に応じてエンドポイントを設計する
SE-3: 取得不足 (Under-fetching)
  • 深刻度: medium
  • 状況: 完全なデータを取得するために複数の API を呼び出す必要がある
  • 原因: エンドポイントを細分化しすぎている、一般的なユースケースを考慮していない
  • 症状:
    • フロントエンドが 1 ページをレンダリングするために 5 回以上の API 呼び出しを必要とする
    • 複雑なフロントエンドのデータ統合ロジック
    • N+1 API リクエストの問題
  • 検出: Promise\.all\(.*fetch.*fetch.*fetch|\.then\(.*fetch
  • 解決策: 集約エンドポイントを設計する、include/expand パラメータを使用する、BFF パターンを検討する
SE-4: 一貫性のないエラー形式
  • 深刻度: high
  • 状況: エンドポイントごとに異なる形式のエラーが返され、クライアントで統一的に処理するのが難しい
  • 原因: 統一されたエラーハンドリングの規約がない、開発者ごとに独自に実装している
  • 症状:
    • あるエラーは error を使い、あるものは message を使い、あるものは errors を使う
    • HTTP ステータスコードの使用が一貫していない
    • クライアントで異なるエラー形式を処理するために多くの if-else を書く必要がある
  • 検出: res\.json\(\{.*error|res\.json\(\{.*message|res\.status\(500\).*error
  • 解決策: 統一されたエラーレスポンス形式を定義する、グローバルエラーハンドラーを使用する、エラーコードシステムを構築する
SE-5: レート制限の欠如
  • 深刻度: critical
  • 状況: API にリクエスト頻度の制限がなく、悪用や攻撃を受けやすい
  • 原因: 「まず作ってから考える」という姿勢、リスクを理解していない
  • 症状:
    • DDoS 攻撃によりサービスが停止する
    • 単一のユーザーがすべてのリソースを消費する
    • クラウドの請求額が爆発する
  • 検出: app\.use\((?!.*rateLimit)|router\.(?!.*limit)|express\(\)(?!.*rate)
  • 解決策: レート制限ミドルウェアを実装する、Redis でリクエストを追跡する、妥当な制限を設定する

バリデーション

V-1: HTTP ステータス 200 でエラーを返すことを禁止
  • タイプ: regex
  • 深刻度: high
  • パターン: res\.json\(\s*\{\s*error|res\.send\(\s*\{\s*error|\.json\(\{.*success:\s*false
  • メッセージ: Error responses should use appropriate HTTP status codes (4xx/5xx)
  • 修正提案: Use res.status(400).json({ error: ... }) for client errors
  • 適用対象: *.ts, *.js
V-2: API パスに動詞を使用
  • タイプ: regex
  • 重大度: medium
  • パターン: (get|create|update|delete|fetch|remove|add)\/|\/get|\/create|\/update|\/delete
  • メッセージ: REST API パスには動詞ではなく名詞を使用してください(代わりに HTTP メソッドを使用してください)
  • 修正提案: /getUsers を GET /users に、/createUser を POST /users に変更してください
  • 適用: *.ts, *.js
V-3: 入力検証の欠如
  • タイプ: regex
  • 重大度: critical
  • パターン: req\.body\.\w+(?!.*validate|.*schema|.*zod|.*joi|.*yup)
  • メッセージ: 明示的な検証なしでリクエストボディにアクセスしています
  • 修正提案: リクエストデータを使用する前に Zod/Joi で検証を追加してください
  • 適用: *.ts, *.js
V-4: HTTP ステータスコードのハードコーディング
  • タイプ: regex
  • 重大度: low
  • パターン: res\.status\(\d{3}\)
  • メッセージ: 可読性のために名前付きステータスコードの使用を検討してください
  • 修正提案: マジックナンバーの代わりに HttpStatus.OK や定数を使用してください
  • 適用: *.ts, *.js
V-5: API バージョン管理の欠如
  • タイプ: regex
  • 重大度: medium
  • パターン: app\.(get|post|put|delete)\s*\(\s*['"]\/(?!v\d|api\/v\d)
  • メッセージ: API ルートにバージョンプレフィックスがありません
  • 修正提案: バージョンプレフィックスを追加してください: /api/v1/users、またはヘッダーベースのバージョン管理を使用してください
  • 適用: *.ts, *.js

コメント