api-design について
API を設計する開発者向けのガイドとリファレンスで、REST、GraphQL、gRPC を扱います。契約優先の設計、バージョン管理、エラー、ページネーション、冪等性の実践に加え、仕様やテンプレートも参照できます。
主な機能
- 01契約優先の API 設計原則
- 02REST、GraphQL、gRPC のガイド
- 03OpenAPI 3.2 の文書化方針
- 04エラー処理とカーソルページネーション
- 05設計チェックリストとテンプレート
活用シーン
- 01新しい API のインターフェース契約の策定
- 02REST API のバージョンとページネーション設計
- 03GraphQL や gRPC のエラー処理の標準化
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ majiayu000/ spellbook --skill api-designインストール後、Claude Code で/api-designを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
reference/comparison.mdgraphql.mdgrpc.mdrest.mdtemplates/graphql/schema.graphqltemplates/grpc/service.prototemplates/openapi/openapi.yaml詳細
API 設計
基本原則
- コントラクトファースト — 実装前に API 仕様を定義します
- OpenAPI 3.2 — REST API のドキュメントには OpenAPI を使用します
- URL バージョニング — パス
/v1/にバージョンを含め、Sunset ヘッダーを使用します - 冪等性 — PUT/DELETE は冪等にし、POST では Idempotency-Key を使用します
- カーソルページネーション — オフセットベースのページネーションは避けます
- RFC 7807 エラー — 標準の Problem Details 形式を使用します
- 後方互換性なし — 非推奨にするのではなく、削除します
クイックリファレンス
使い分け
| シナリオ | 選択肢 | 理由 |
|---|---|---|
| パブリック API / MVP | REST | シンプルで汎用性が高く、デバッグも容易です |
| フロントエンド主導/モバイル | 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
関連項目
- reference/rest.md — REST の詳細
- reference/graphql.md — GraphQL のパターン
- reference/grpc.md — gRPC のパターン
- reference/comparison.md — 選択ガイド
- templates/openapi/openapi.yaml — OpenAPI のスターターコントラクト
- templates/graphql/schema.graphql — GraphQL スキーマのスターター
- templates/grpc/service.proto — gRPC サービスのスターター
コメント
Markdown 対応