mcpField
A
api-design

api-design

github.com/majiayu000開発29091 回インストール3 日前に更新
リポジトリ

api-design について

API を設計する開発者向けのガイドとリファレンスで、REST、GraphQL、gRPC を扱います。契約優先の設計、バージョン管理、エラー、ページネーション、冪等性の実践に加え、仕様やテンプレートも参照できます。

主な機能

  1. 01契約優先の API 設計原則
  2. 02REST、GraphQL、gRPC のガイド
  3. 03OpenAPI 3.2 の文書化方針
  4. 04エラー処理とカーソルページネーション
  5. 05設計チェックリストとテンプレート

活用シーン

  1. 01新しい API のインターフェース契約の策定
  2. 02REST API のバージョンとページネーション設計
  3. 03GraphQL や gRPC のエラー処理の標準化

インストール

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

$npx skills add https://github.com/majiayu000/spellbook --skill api-design

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

同梱ファイル

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

reference/
comparison.md
graphql.md
grpc.md
rest.md
templates/graphql/
schema.graphql
templates/grpc/
service.proto
templates/openapi/
openapi.yaml

詳細

API 設計

基本原則

  • コントラクトファースト — 実装前に API 仕様を定義します
  • OpenAPI 3.2 — REST API のドキュメントには OpenAPI を使用します
  • URL バージョニング — パス /v1/ にバージョンを含め、Sunset ヘッダーを使用します
  • 冪等性 — PUT/DELETE は冪等にし、POST では Idempotency-Key を使用します
  • カーソルページネーション — オフセットベースのページネーションは避けます
  • RFC 7807 エラー — 標準の Problem Details 形式を使用します
  • 後方互換性なし — 非推奨にするのではなく、削除します

クイックリファレンス

使い分け
シナリオ選択肢理由
パブリック API / MVPRESTシンプルで汎用性が高く、デバッグも容易です
フロントエンド主導/モバイルGraphQL必要なデータだけを取得できます
マイクロサービス間の内部通信gRPC高いパフォーマンスと強い型付けを実現します
リアルタイムデータgRPC / GraphQL Subscriptions双方向ストリーミングが可能です

REST API 設計

リソースの命名
# Good
GET  /users              # List users
GET  /users/123          # Get user
POST /users              # Create user
PUT  /users/123          # Replace user
PATCH /users/123         # Update user
DELETE /users/123        # Delete user

# Nested resources
GET /users/123/orders    # User's orders

# Actions (when CRUD doesn't fit)
POST /users/123/activate # Action on resource

# Query parameters for filtering
GET /users?status=active&role=admin&limit=20
HTTP メソッド
メソッド目的冪等安全
GET読み取りはいはい
POST作成いいえいいえ
PUT置き換えはいいいえ
PATCH更新いいえいいえ
DELETE削除はいいいえ
ステータスコード
# Success
200 OK              - Successful GET/PUT/PATCH
201 Created         - Successful POST (include Location header)
204 No Content      - Successful DELETE

# Client Errors
400 Bad Request     - Malformed request syntax
401 Unauthorized    - Missing/invalid authentication
403 Forbidden       - Authenticated but not authorized
404 Not Found       - Resource doesn't exist
409 Conflict        - Duplicate/conflict (e.g., unique constraint)
422 Unprocessable   - Validation failed
429 Too Many        - Rate limited

# Server Errors
500 Internal Error  - Unexpected server error
503 Unavailable     - Service temporarily down
エラーレスポンス(RFC 7807)
{
  "type": "https://api.example.com/errors/validation",
  "title": "Validation Error",
  "status": 422,
  "detail": "The request contains invalid parameters",
  "instance": "/users/123",
  "errors": [
    { "field": "email", "message": "Invalid email format" },
    { "field": "age", "message": "Must be positive integer" }
  ]
}
ページネーション(カーソルベース)
// Request
GET /users?limit=20&cursor=eyJpZCI6MTAwfQ

// Response
{
  "data": [...],
  "pagination": {
    "next_cursor": "eyJpZCI6MTIwfQ",
    "prev_cursor": "eyJpZCI6ODB9",
    "has_next": true,
    "has_prev": true,
    "limit": 20
  }
}
バージョニング
# URL versioning (recommended)
GET /v1/users
GET /v2/users

# Deprecation headers
Sunset: Sat, 31 Dec 2025 23:59:59 GMT
Deprecation: true
Link: </v2/users>; rel="successor-version"
冪等性
# For non-idempotent operations (POST)
POST /orders
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000

# Server stores result and returns same response for duplicate key

GraphQL 設計

スキーマの原則
  • ドメイン主導 — スキーマはデータベースではなく、ビジネスドメインを反映します
  • 説明的な名前 — 監視しやすい、明確なフィールド名/型名を使用します
  • ネストを制限 — 深いネストはパフォーマンスを低下させます
  • @key を使用 — Federation 用にエンティティ識別子を指定します
型定義
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!
}

type User @key(fields: "id") {
  id: ID!
  email: String!
  name: String!
  orders(first: Int, after: String): OrderConnection!
  createdAt: DateTime!
}

# Relay-style pagination
type UserConnection {
  edges: [UserEdge!]!
  pageInfo: PageInfo!
  totalCount: Int!
}

type UserEdge {
  node: User!
  cursor: String!
}

type PageInfo {
  hasNextPage: Boolean!
  hasPreviousPage: Boolean!
  startCursor: String
  endCursor: String
}
エラー処理
type Mutation {
  createUser(input: CreateUserInput!): CreateUserPayload!
}

# Union for typed errors
union CreateUserPayload = User | ValidationError | ConflictError

type ValidationError {
  message: String!
  field: String
  code: String!
}

type ConflictError {
  message: String!
  existingId: ID!
}
N+1 の防止
// Use DataLoader for batching
const userLoader = new DataLoader(async (ids: string[]) => {
  const users = await db.user.findMany({
    where: { id: { in: ids } }
  });
  return ids.map(id => users.find(u => u.id === id));
});

// Resolver
const resolvers = {
  Order: {
    user: (order) => userLoader.load(order.userId),
  },
};

gRPC 設計

Proto 定義
syntax = "proto3";

package api.v1;

import "google/protobuf/timestamp.proto";
import "google/protobuf/empty.proto";

service UserService {
  // Unary
  rpc GetUser(GetUserRequest) returns (User);
  rpc CreateUser(CreateUserRequest) returns (User);

  // Server streaming
  rpc ListUsers(ListUsersRequest) returns (stream User);

  // Client streaming
  rpc BatchCreateUsers(stream CreateUserRequest) returns (BatchCreateResponse);

  // Bidirectional streaming
  rpc SyncUsers(stream UserUpdate) returns (stream UserUpdate);
}

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

message GetUserRequest {
  string id = 1;
}

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

message UserFilter {
  optional string status = 1;
  optional string role = 2;
}
エラー処理
// Use Google's richer error model
import "google/rpc/status.proto";
import "google/rpc/error_details.proto";

// For streaming: embed errors in response
message StreamResponse {
  oneof result {
    User user = 1;
    StreamError error = 2;
  }
}

message StreamError {
  string code = 1;
  string message = 2;
  map<string, string> details = 3;
}
デッドラインとリトライ
// Always set deadlines
const deadline = new Date();
deadline.setSeconds(deadline.getSeconds() + 5);

const user = await client.getUser(
  { id: '123' },
  { deadline }
);

// Configure retry policy
const retryPolicy = {
  maxAttempts: 3,
  initialBackoff: '0.1s',
  maxBackoff: '1s',
  backoffMultiplier: 2,
  retryableStatusCodes: ['UNAVAILABLE', 'DEADLINE_EXCEEDED'],
};

レート制限

ヘッダー
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1640995200
Retry-After: 60
レスポンス(429)
{
  "type": "https://api.example.com/errors/rate-limited",
  "title": "Rate Limit Exceeded",
  "status": 429,
  "detail": "You have exceeded the rate limit of 100 requests per minute",
  "retryAfter": 60
}

チェックリスト

## Design
- [ ] API spec defined before implementation
- [ ] Resources use plural nouns
- [ ] Correct HTTP methods/status codes
- [ ] RFC 7807 error format

## Features
- [ ] Cursor-based pagination
- [ ] Rate limiting with headers
- [ ] Idempotency keys for POST
- [ ] API versioning strategy

## Documentation
- [ ] OpenAPI/GraphQL schema published
- [ ] Examples for all endpoints
- [ ] Error codes documented

## Operations
- [ ] Request/response logging
- [ ] Latency and error rate metrics
- [ ] Deprecation notices for old versions

関連項目

コメント