api-design について
api-designは、一貫性があり保守しやすいREST APIの設計を支援するスキルです。エンドポイント規約、エラー処理、ページネーション、バージョニング、ドキュメントを扱い、詳細な参照資料も備えています。
主な機能
- 01RESTリソースとHTTPメソッドの規約
- 02一貫したエラーレスポンス形式
- 03カーソル式とオフセット式ページネーション
- 04フィルタリング、ソート、レート制限
- 05APIバージョニングとドキュメントの指針
活用シーン
- 01新しいAPIのエンドポイント設計
- 02エラーとページネーション動作の統一
- 03破壊的変更とAPIバージョンの計画
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ scientiacapital/ skills --skill api-designインストール後、Claude Code で/api-designを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
config.jsonreference/checklist.mddocumentation.mderror-handling.mdfiltering-sorting.mdpagination.mdrate-limiting.mdrest-patterns.mdversioning.md詳細
優れた API 設計とは、正しいことを簡単にし、誤ったことを難しくするものです。このスキルは、使うのも保守するのも快適な API を作成する手助けをします。
<quick_start>
リソースの命名: 名詞、複数形、小文字、ハイフン区切り(/users、/blog-posts)
HTTP メソッド: GET(読み取り)、POST(作成)、PUT(置換)、PATCH(更新)、DELETE(削除)
ステータスコード: 200 OK、201 Created、204 No Content、400 Bad Request、401 Unauthorized、403 Forbidden、404 Not Found、429 Rate Limited
ページネーション: リアルタイムデータにはカーソルベースを推奨。?limit=20&cursor=xxx を使用します
バージョニング: URL パス(/api/v1/)が最も明確なアプローチです
</quick_start>
<success_criteria> API 設計は次の場合に成功しています:
- リソースが一貫した命名で複数形の名詞を使用している
- HTTP メソッドがセマンティクスに一致している(GET=読み取り、POST=作成など)
- エラーレスポンスが code、message、details、requestId を含む一貫した形式に従っている
- ページネーションが実装されている(カーソルベースまたはオフセットベース)
- レート制限ヘッダーが含まれている(X-RateLimit-Limit、Remaining、Reset)
- 破壊的変更が必要になる前にバージョニング戦略が定義されている </success_criteria>
<core_principles>
API 設計の原則
- 一貫性 - どこでも同じパターン(命名、エラー、ページネーション)
- 予測可能性 - 開発者が動作を推測できる
- シンプルさ - 簡単なケースは簡単に、複雑なケースも可能に
- 後方互換性 - 既存のクライアントを壊さない
- 自己文書化 - 明確な命名、役立つエラーメッセージ </core_principles>
<rest_basics>
RESTful の規約
リソースの命名
# GOOD: Nouns, plural, lowercase, hyphenated
GET /users
GET /users/{id}
GET /users/{id}/posts
GET /blog-posts
GET /api/v1/user-preferences
# BAD: Verbs, singular, mixed case, underscores
GET /getUser
GET /user/{id}
GET /User/{id}/getPosts
GET /blog_posts
HTTP メソッド
| メソッド | 目的 | 冪等 | 安全 | リクエストボディ |
|---|---|---|---|---|
| GET | リソースの読み取り | はい | はい | いいえ |
| POST | リソースの作成 | いいえ | いいえ | はい |
| PUT | リソースの置換 | はい | いいえ | はい |
| PATCH | 部分更新 | いいえ* | いいえ | はい |
| DELETE | リソースの削除 | はい | いいえ | いいえ |
*同じパッチを適用する場合、PATCH は冪等です
CRUD 操作
# Collection operations
GET /users # List all users
POST /users # Create a user
# Single resource operations
GET /users/{id} # Get one user
PUT /users/{id} # Replace user
PATCH /users/{id} # Update user fields
DELETE /users/{id} # Delete user
# Nested resources
GET /users/{id}/posts # User's posts
POST /users/{id}/posts # Create post for user
GET /posts/{id}/comments # Post's comments
アクション(非 CRUD 操作)
# When you need actions, use verbs as sub-resources
POST /users/{id}/activate
POST /users/{id}/deactivate
POST /orders/{id}/cancel
POST /invoices/{id}/send
POST /auth/login
POST /auth/logout
POST /auth/refresh
ステータスコード
| コード | 意味 | 使用する場面 |
|---|---|---|
| 200 | OK | 成功した GET、PUT、PATCH |
| 201 | Created | 作成を伴う成功した POST |
| 204 | No Content | 成功した DELETE |
| 400 | Bad Request | 無効な入力、バリデーションエラー |
| 401 | Unauthorized | 認証の欠落/無効 |
| 403 | Forbidden | 認証済みだが認可されていない |
| 404 | Not Found | リソースが存在しない |
| 409 | Conflict | 重複、状態の競合 |
| 422 | Unprocessable | バリデーション失敗(400 の代替) |
| 429 | Too Many Requests | レート制限 |
| 500 | Server Error | 予期しないサーバーエラー |
| </rest_basics> |
<error_handling>
エラーレスポンス
一貫したエラー形式
// Standard error response
interface ErrorResponse {
error: {
code: string; // Machine-readable code
message: string; // Human-readable message
details?: ErrorDetail[]; // Field-level errors
requestId?: string; // For support/debugging
};
}
interface ErrorDetail {
field: string;
message: string;
code: string;
}
例
// 400 Bad Request - Validation error
{
"error": {
"code": "validation_error",
"message": "Invalid request parameters",
"details": [
{ "field": "email", "message": "Invalid email format", "code": "invalid_format" },
{ "field": "age", "message": "Must be at least 13", "code": "min_value" }
],
"requestId": "req_abc123"
}
}
// 401 Unauthorized
{
"error": {
"code": "unauthorized",
"message": "Invalid or expired authentication token",
"requestId": "req_abc123"
}
}
// 403 Forbidden
{
"error": {
"code": "forbidden",
"message": "You don't have permission to access this resource",
"requestId": "req_abc123"
}
}
// 404 Not Found
{
"error": {
"code": "not_found",
"message": "User not found",
"requestId": "req_abc123"
}
}
// 429 Rate Limited
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Please retry after 60 seconds",
"requestId": "req_abc123"
}
}
実装
// Error class
class ApiError extends Error {
constructor(
public statusCode: number,
public code: string,
message: string,
public details?: ErrorDetail[]
) {
super(message);
}
}
// Error handler middleware
function errorHandler(error: Error, req: Request, res: Response) {
const requestId = req.headers['x-request-id'] || crypto.randomUUID();
if (error instanceof ApiError) {
return res.status(error.statusCode).json({
error: {
code: error.code,
message: error.message,
details: error.details,
requestId,
},
});
}
// Log unexpected errors
logger.error('Unexpected error', { error, requestId });
// Don't expose internal details
return res.status(500).json({
error: {
code: 'internal_error',
message: 'An unexpected error occurred',
requestId,
},
});
}
</error_handling>
## ページネーションカーソルベース(推奨)
// Request
GET /posts?limit=20&cursor=eyJpZCI6MTAwfQ
// Response
{
"data": [...],
"pagination": {
"hasMore": true,
"nextCursor": "eyJpZCI6MTIwfQ",
"prevCursor": "eyJpZCI6MTAwfQ"
}
}
長所: 一貫した結果、リアルタイムデータに対応 短所: N ページ目にジャンプできない
オフセットベース
// Request
GET /posts?page=2&limit=20
// or
GET /posts?offset=20&limit=20
// Response
{
"data": [...],
"pagination": {
"page": 2,
"limit": 20,
"total": 150,
"totalPages": 8
}
}
長所: 任意のページにジャンプできる 短所: リアルタイムデータでは一貫性がない、大きなテーブルでは遅い
実装(カーソル)
// Encode/decode cursor
function encodeCursor(data: object): string {
return Buffer.from(JSON.stringify(data)).toString('base64url');
}
function decodeCursor(cursor: string): object {
return JSON.parse(Buffer.from(cursor, 'base64url').toString());
}
// Query with cursor
async function getPosts(limit: number, cursor?: string) {
const where: any = {};
if (cursor) {
const { id } = decodeCursor(cursor);
where.id = { lt: id };
}
const posts = await db.post.findMany({
where,
orderBy: { id: 'desc' },
take: limit + 1, // Fetch one extra to check hasMore
});
const hasMore = posts.length > limit;
const data = hasMore ? posts.slice(0, -1) : posts;
return {
data,
pagination: {
hasMore,
nextCursor: hasMore ? encodeCursor({ id: data[data.length - 1].id }) : null,
},
};
}
## フィルタリングとソート
クエリパラメータを使用します: ?status=active、?sort=-created_at(降順には接頭辞 -)、?fields=id,name,email。Zod スキーマで検証し、パースされたフィルターから Prisma の where 句を構築します。
完全なクエリパラメータのパターンと実装については reference/filtering-sorting.md を参照してください。
URL パスバージョニング(推奨)
GET /api/v1/users
GET /api/v2/users
長所: 明確で理解しやすい 短所: メンテナンスが増える
ヘッダーバージョニング
GET /api/users
Accept: application/vnd.api+json; version=2
長所: クリーンな URL 短所: 見えにくく、テストが難しい
バージョンを切り替えるタイミング
次の場合は新しいバージョンを作成します。
- レスポンスからフィールドを削除する場合
- フィールドの型や形式を変更する場合
- エンドポイントを削除する場合
- 認証を変更する場合
次の場合は新しいバージョンを作成しません。
- 新しい任意フィールドを追加する場合
- 新しいエンドポイントを追加する場合
- 新しい任意パラメーターを追加する場合
- バグを修正する場合
<rate_limiting>
レート制限
X-RateLimit-Limit、X-RateLimit-Remaining、X-RateLimit-Reset ヘッダーを含めます。制限を超えた場合は、Retry-After とともに 429 を返します。スライディングウィンドウアルゴリズム(例:@upstash/ratelimit)を使用します。
ティアの定義と実装については、reference/rate-limiting.md を参照してください。
</rate_limiting>
| トピック | リファレンスファイル | 読み込むタイミング |
|---|---|---|
| REST パターン | reference/rest-patterns.md | エンドポイントの設計時 |
| エラー処理 | reference/error-handling.md | エラーレスポンスの設計時 |
| ページネーション | reference/pagination.md | リストエンドポイントの設計時 |
| フィルタリングとソート | reference/filtering-sorting.md | クエリパラメーターの設計時 |
| レート制限 | reference/rate-limiting.md | リクエストのスロットリング時 |
| バージョン管理 | reference/versioning.md | API の進化時 |
| ドキュメント | reference/documentation.md | OpenAPI、ドキュメントの作成時 |
| チェックリスト | reference/checklist.md | リリース前の検証時 |
読み込むには: 特定のトピックを指定して依頼するか、コンテキストから必要な内容を判断してください。
結果サイドカーの出力
最後の手順として、~/.claude/skill-analytics/last-outcome-api-design.json に書き込みます。
{"ts":"[UTC ISO8601]","skill":"api-design","version":"1.0.0","variant":"default",
"status":"[success|partial|error]","runtime_ms":[estimated ms from start],
"metrics":{"endpoints_designed":[n],"patterns_applied":[n]},
"error":null,"session_id":"[YYYY-MM-DD]"}
一部のステージで失敗したものの結果が生成された場合は、ステータスに "partial" を使用します。出力が生成されなかった場合に限り、"error" を使用します。