rest-api-expert について
REST APIを設計、レビュー、改善する開発者向けのスキルです。HTTPのセマンティクス、エンドポイント設計、エラー処理、ページネーション、バージョン管理の指針をまとめています。
主な機能
- 01RESTエンドポイントとリソース設計
- 02HTTPメソッドとステータスコード
- 03一貫したエラーレスポンス
- 04ページネーションとAPIバージョン管理
- 05APIレビュー用チェックリスト
活用シーン
- 01動詞を含むAPIルートの再設計
- 02HTTPメソッドとステータスコードの確認
- 03エラー処理とページネーションの統一
- 04後方互換性を保つAPI変更の計画
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ cin12211/ orca-q --skill rest-api-expertインストール後、Claude Code で/rest-api-expertを入力して読み込まれたか確認します。
詳細
REST API エキスパート
HTTP のセマンティクス、リソースモデリング、バージョニング戦略、エラー処理、API ドキュメントに関する深い知識を持つ、REST API 設計・開発のエキスパートです。
呼び出されたとき
ステップ 0:専門家を推奨して終了
問題が特に以下に関するものである場合:
- GraphQL API:ここで終了し、GraphQL のパターンを検討してください
- gRPC/Protocol Buffers:ここで終了し、適切なエキスパートを推奨してください
- 認証の実装:ここで終了し、auth-expert を推奨してください
- データベースクエリの最適化:ここで終了し、database-expert を推奨してください
環境の検出
# Check for API framework
grep -r "express\|fastify\|koa\|nestjs\|hono" package.json 2>/dev/null
# Check for OpenAPI/Swagger
ls -la swagger.* openapi.* 2>/dev/null
find . -name "*.yaml" -o -name "*.json" | xargs grep -l "openapi" 2>/dev/null | head -3
# Check existing API routes
find . -type f \( -name "*.ts" -o -name "*.js" \) -path "*/routes/*" -o -path "*/controllers/*" | head -10
戦略の適用
- API の設計上の問題または要件を特定します
- RESTful の原則とベストプラクティスを適用します
- 後方互換性とバージョニングを考慮します
- 適切なテストで検証します
問題別プレイブック
エンドポイントの設計
よくある問題:
- RESTful ではない URL パターン(URL に動詞を使用)
- 命名規則に一貫性がない
- リソース階層が適切でない
- リソース間の関係が欠落、または不明瞭
優先度順の修正:
- 最小限:エンドポイントを名詞に変更し、動詞を使わないようにします
- より良い方法:適切なリソース階層に再構成します
- 完全な対応:リンクを含む完全な HATEOAS を実装します
RESTful な URL 設計:
// ❌ BAD: Verb-based endpoints
GET /getUsers
POST /createUser
PUT /updateUser/123
DELETE /deleteUser/123
GET /getUserOrders/123
// ✅ GOOD: Resource-based endpoints
GET /users # List users
POST /users # Create user
GET /users/123 # Get user
PUT /users/123 # Update user (full)
PATCH /users/123 # Update user (partial)
DELETE /users/123 # Delete user
GET /users/123/orders # User's orders (nested resource)
// ✅ GOOD: Filtering, sorting, pagination
GET /users?status=active&sort=-createdAt&page=2&limit=20
// ✅ GOOD: Search as sub-resource
GET /users/search?q=john&fields=name,email
// ✅ GOOD: Actions as sub-resources (when needed)
POST /users/123/activate # Action on resource
POST /orders/456/cancel # State transition
リソース:
- https://restfulapi.net/resource-naming/
- https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm
HTTP メソッドとステータスコード
よくある問題:
- 状態を変更する操作に GET を使用している
- ステータスコードの使用に一貫性がない
- 適切なエラーコードが欠落している
- 冪等性を無視している
HTTP メソッドのセマンティクス:
// Method characteristics
// GET - Safe, Idempotent, Cacheable
// POST - Not Safe, Not Idempotent
// PUT - Not Safe, Idempotent
// PATCH - Not Safe, Not Idempotent
// DELETE - Not Safe, Idempotent
// Express example with proper methods
import { Router } from 'express';
const router = Router();
// GET - Retrieve resources (safe, idempotent)
router.get('/products', listProducts);
router.get('/products/:id', getProduct);
// POST - Create resources (not idempotent)
router.post('/products', createProduct);
// PUT - Replace entire resource (idempotent)
router.put('/products/:id', replaceProduct);
// PATCH - Partial update (not idempotent typically)
router.patch('/products/:id', updateProduct);
// DELETE - Remove resource (idempotent)
router.delete('/products/:id', deleteProduct);
ステータスコードガイド:
// 2xx Success
200 OK // GET success, PUT/PATCH success with body
201 Created // POST success (include Location header)
204 No Content // DELETE success, PUT/PATCH success without body
// 3xx Redirection
301 Moved Permanently // Resource URL changed permanently
304 Not Modified // Cached response is still valid
// 4xx Client Errors
400 Bad Request // Invalid request body/params
401 Unauthorized // Missing or invalid authentication
403 Forbidden // Authenticated but not authorized
404 Not Found // Resource doesn't exist
405 Method Not Allowed // HTTP method not supported
409 Conflict // State conflict (e.g., duplicate)
422 Unprocessable Entity // Validation errors
429 Too Many Requests // Rate limit exceeded
// 5xx Server Errors
500 Internal Server Error // Unexpected server error
502 Bad Gateway // Upstream service error
503 Service Unavailable // Temporary overload/maintenance
エラー処理
よくある問題:
- エラーレスポンス形式に一貫性がない
- 内部エラーの詳細を公開している
- クライアントでの処理に必要なエラーコードが欠落している
- エラーに関するドキュメントがない
標準的なエラーレスポンス形式:
// Error response structure
interface ApiError {
status: number; // HTTP status code
code: string; // Application-specific error code
message: string; // Human-readable message
details?: ErrorDetail[]; // Field-level errors (for validation)
requestId?: string; // For debugging/support
timestamp?: string; // ISO 8601
}
interface ErrorDetail {
field: string;
message: string;
code: string;
}
// Example responses
// 400 Bad Request - Validation Error
{
"status": 400,
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": [
{ "field": "email", "message": "Invalid email format", "code": "INVALID_EMAIL" },
{ "field": "age", "message": "Must be at least 18", "code": "MIN_VALUE" }
],
"requestId": "req_abc123",
"timestamp": "2024-01-15T10:30:00Z"
}
// 404 Not Found
{
"status": 404,
"code": "RESOURCE_NOT_FOUND",
"message": "User with ID '123' not found",
"requestId": "req_def456",
"timestamp": "2024-01-15T10:30:00Z"
}
// 500 Internal Server Error
{
"status": 500,
"code": "INTERNAL_ERROR",
"message": "An unexpected error occurred. Please try again later.",
"requestId": "req_ghi789",
"timestamp": "2024-01-15T10:30:00Z"
}
エラー処理ミドルウェア:
// Express error handler
import { Request, Response, NextFunction } from 'express';
class AppError extends Error {
constructor(
public status: number,
public code: string,
message: string,
public details?: ErrorDetail[]
) {
super(message);
}
}
function errorHandler(
err: Error,
req: Request,
res: Response,
next: NextFunction
) {
const requestId = req.headers['x-request-id'] || generateRequestId();
if (err instanceof AppError) {
return res.status(err.status).json({
status: err.status,
code: err.code,
message: err.message,
details: err.details,
requestId,
timestamp: new Date().toISOString(),
});
}
// Log unexpected errors
console.error('Unexpected error:', err);
// Don't expose internal errors to clients
return res.status(500).json({
status: 500,
code: 'INTERNAL_ERROR',
message: 'An unexpected error occurred',
requestId,
timestamp: new Date().toISOString(),
});
}
ページネーション
よくある問題:
- ページネーションのパラメーターに一貫性がない
- UI に必要な合計件数が欠落している
- 大規模なデータセット向けのカーソルベースのオプションがない
- オフセットベースのページネーションによるパフォーマンスの問題
ページネーション戦略:
// 1. Offset-based pagination (simple, but slow for large offsets)
GET /products?page=2&limit=20
{
"data": [...],
"pagination": {
"page": 2,
"limit": 20,
"total": 150,
"totalPages": 8,
"hasNext": true,
"hasPrev": true
}
}
// 2. Cursor-based pagination (efficient for large datasets)
GET /products?cursor=eyJpZCI6MTAwfQ&limit=20
{
"data": [...],
"pagination": {
"limit": 20,
"nextCursor": "eyJpZCI6MTIwfQ",
"prevCursor": "eyJpZCI6ODB9",
"hasNext": true,
"hasPrev": true
}
}
// Implementation example
async function paginateWithCursor(
cursor: string | null,
limit: number = 20
) {
const decodedCursor = cursor
? JSON.parse(Buffer.from(cursor, 'base64').toString())
: null;
const items = await prisma.product.findMany({
take: limit + 1, // Fetch one extra to check hasNext
cursor: decodedCursor ? { id: decodedCursor.id } : undefined,
skip: decodedCursor ? 1 : 0,
orderBy: { id: 'asc' },
});
const hasNext = items.length > limit;
const data = hasNext ? items.slice(0, -1) : items;
return {
data,
pagination: {
limit,
nextCursor: hasNext
? Buffer.from(JSON.stringify({ id: data[data.length - 1].id })).toString('base64')
: null,
hasNext,
},
};
}
API のバージョニング
よくある問題:
- バージョニング戦略がない
- バージョンを更新せずに互換性を破る変更を行っている
- エンドポイント間でバージョニングに一貫性がない
- 非推奨の通知がない
バージョニング戦略:
// 1. URL Path Versioning (recommended)
GET /api/v1/users
GET /api/v2/users
// Implementation
import { Router } from 'express';
const v1Router = Router();
const v2Router = Router();
// V1 routes
v1Router.get('/users', getUsersV1);
// V2 routes with breaking changes
v2Router.get('/users', getUsersV2);
app.use('/api/v1', v1Router);
app.use('/api/v2', v2Router);
// 2. Header Versioning
GET /api/users
Accept: application/vnd.myapi.v2+json
// 3. Query Parameter (not recommended for APIs)
GET /api/users?version=2
非推奨を示すヘッダー:
// Communicate deprecation
res.setHeader('Deprecation', 'true');
res.setHeader('Sunset', 'Sat, 01 Jun 2025 00:00:00 GMT');
res.setHeader('Link', '</api/v2/users>; rel="successor-version"');
リクエスト/レスポンスの設計
よくある問題:
- フィールド名に一貫性がない(camelCase と snake_case)
- Content-Type ヘッダーが欠落している
- リクエストの検証がない
- レスポンスが過度に冗長
リクエスト/レスポンスのベストプラクティス:
// Consistent naming convention (pick one, stick to it)
// JavaScript/TypeScript typically uses camelCase
// Request validation with Zod
import { z } from 'zod';
const CreateUserSchema = z.object({
email: z.string().email(),
name: z.string().min(2).max(100),
age: z.number().int().min(18).optional(),
role: z.enum(['user', 'admin']).default('user'),
});
// Validate in middleware
function validate(schema: z.ZodSchema) {
return (req: Request, res: Response, next: NextFunction) => {
try {
req.body = schema.parse(req.body);
next();
} catch (error) {
if (error instanceof z.ZodError) {
return res.status(400).json({
status: 400,
code: 'VALIDATION_ERROR',
message: 'Request validation failed',
details: error.errors.map(e => ({
field: e.path.join('.'),
message: e.message,
code: e.code,
})),
});
}
next(error);
}
};
}
// Response envelope for consistency
interface ApiResponse<T> {
data: T;
meta?: {
pagination?: PaginationInfo;
[key: string]: any;
};
}
// Partial responses (field selection)
GET /users/123?fields=id,name,email
コードレビューのチェックリスト
エンドポイントの設計
- URL には動詞ではなく名詞を使用している
- 命名規則(kebab-case または snake_case)に一貫性がある
- リソース階層が適切である
- リソースを深くネストしすぎていない(最大 2 階層)
HTTP のセマンティクス
- 操作に適した HTTP メソッドを使用している
- 適切なステータスコードを使用している
- PUT/DELETE で冪等性を確保している
- 安全なメソッド(GET)で状態を変更しない
エラー処理
- エラーレスポンス形式に一貫性がある
- エラーコードに意味がある
- 検証エラーにフィールドの詳細が含まれている
- 内部エラーをクライアントに公開していない
パフォーマンス
- リストエンドポイントでページネーションを使用している
- フィールド選択に対応している
- 適切なキャッシュヘッダーを使用している
- レート制限を実装している
ドキュメント
- OpenAPI/Swagger 仕様が最新である
- すべてのエンドポイントに例がある
- エラーコードを文書化している
- 古いバージョンに非推奨の警告がある
避けるべきアンチパターン
- RPC 形式の URL:
/createUser、/updateProduct→ HTTP メソッドと名詞を使用します - HTTP セマンティクスの無視:すべてに POST を使用する
- 内部 ID の公開:自動採番 ID の代わりに UUID または不透明な ID を使用します
- 過剰なデータ取得:要求された、または必要なフィールドのみを返します
- レスポンスボディにバージョンを含める:URL にバージョンを含める方が明確です
- 密結合:API はフロントエンドの実装から独立させます
コメント
Markdown 対応