api-design について
api-designは、保守しやすいRESTなどのAPIを設計する開発者向けのリファレンススキルです。リソース命名からセキュリティ、プロトコル、配信パターンまで扱います。
主な機能
- 01RESTのリソースとメソッドの規約
- 02ステータスコードとレスポンス形式
- 03ページネーション、フィルタリング、ソート、バージョン管理
- 04OpenAPI、gRPC、tRPCのガイダンス
- 05認証、レート制限、セキュリティ
- 06Webhookの署名、再試行、冪等性
活用シーン
- 01新しいREST APIのエンドポイント設計
- 02ページネーションとバージョン管理方式の選択
- 03認証とセキュリティ要件の整理
- 04信頼性の高いWebhook配信の設計
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ travisjneuman/ .claude --skill api-designインストール後、Claude Code で/api-designを入力して読み込まれたか確認します。
詳細
API 設計ガイド
開発者にとって使いやすく、保守しやすい API を設計するためのベストプラクティスです。
REST API の原則
リソースの命名
動詞ではなく名詞を使います:
✓ GET /users
✓ GET /users/123
✓ GET /users/123/orders
✗ GET /getUsers
✗ GET /fetchUserById
✗ POST /createNewOrder
複数形の名詞を使います:
✓ /users
✓ /orders
✓ /products
✗ /user
✗ /order
階層関係:
/users/{userId}/orders # User's orders
/users/{userId}/orders/{orderId} # Specific order
HTTP メソッド
| メソッド | 用途 | 冪等性 | リクエストボディ |
|---|---|---|---|
| GET | リソースの取得 | はい | いいえ |
| POST | リソースの作成 | いいえ | はい |
| PUT | リソース全体の置換 | はい | はい |
| PATCH | リソースの部分更新 | いいえ | はい |
| DELETE | リソースの削除 | はい | いいえ |
ステータスコード
成功(2xx):
200 OK- リクエストが成功しました201 Created- リソースが作成されました(Location ヘッダーを返します)204 No Content- レスポンスボディなしで成功しました
クライアントエラー(4xx):
400 Bad Request- リクエストの形式が正しくありません401 Unauthorized- 認証が必要です403 Forbidden- 権限がありません404 Not Found- リソースが存在しません409 Conflict- 状態が競合しています422 Unprocessable Entity- 検証に失敗しました429 Too Many Requests- レート制限を超過しました
サーバーエラー(5xx):
500 Internal Server Error- 予期しないエラーです503 Service Unavailable- 一時的にサービスを利用できません
レスポンス形式
成功レスポンス:
{
"data": {
"id": "123",
"name": "John Doe",
"email": "[email protected]"
}
}
コレクションレスポンス:
{
"data": [
{ "id": "1", "name": "Item 1" },
{ "id": "2", "name": "Item 2" }
],
"meta": {
"page": 1,
"perPage": 20,
"total": 100,
"totalPages": 5
},
"links": {
"self": "/items?page=1",
"next": "/items?page=2",
"prev": null
}
}
エラーレスポンス:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Validation failed",
"details": [
{
"field": "email",
"code": "INVALID_FORMAT",
"message": "Must be a valid email address"
}
]
}
}
ページネーション
オフセット方式(シンプルですが、大規模なデータセットでは遅くなります):
GET /users?page=2&perPage=20
GET /users?offset=40&limit=20
カーソル方式(効率的で、推奨されます):
GET /users?cursor=eyJpZCI6MTIzfQ&limit=20
レスポンスには次のカーソルが含まれます:
{
"data": [...],
"meta": {
"nextCursor": "eyJpZCI6MTQzfQ",
"hasMore": true
}
}
フィルタリング、並べ替え、フィールド
フィルタリング:
GET /users?status=active
GET /users?created_after=2024-01-01
GET /users?role=admin,moderator
並べ替え:
GET /users?sort=name
GET /users?sort=-created_at # Descending
GET /users?sort=status,-created_at # Multiple fields
フィールドの選択:
GET /users?fields=id,name,email
GET /users?include=orders,profile
バージョニング
URL パス(推奨):
/api/v1/users
/api/v2/users
ヘッダー:
Accept: application/vnd.api+json;version=2
OpenAPI 仕様
openapi: 3.0.3
info:
title: My API
version: 1.0.0
description: API for managing users
servers:
- url: https://api.example.com/v1
paths:
/users:
get:
summary: List users
tags: [Users]
parameters:
- name: page
in: query
schema:
type: integer
default: 1
responses:
"200":
description: List of users
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
$ref: "#/components/schemas/User"
components:
schemas:
User:
type: object
required: [id, email]
properties:
id:
type: string
email:
type: string
format: email
name:
type: string
認証
API キー(シンプルで、サーバー間通信向け):
Authorization: Api-Key YOUR_API_KEY
Bearer トークン(JWT、OAuth):
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
OpenAPI への記載:
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
security:
- bearerAuth: []
レート制限
レスポンスにヘッダーを含めます:
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1640995200
制限を超過した場合は、429 Too Many Requests を返します。
セキュリティチェックリスト
- HTTPS のみを使用
- 保護されたルートで認証を実施
- 入力値の検証
- 出力のエンコード
- レート制限
- CORS の設定
- URL に機密データを含めない
- 監査ログの記録
gRPC と Protocol Buffers
Proto 定義
syntax = "proto3";
package user.v1;
service UserService {
rpc GetUser(GetUserRequest) returns (User);
rpc ListUsers(ListUsersRequest) returns (ListUsersResponse);
rpc CreateUser(CreateUserRequest) returns (User);
rpc StreamUpdates(StreamRequest) returns (stream UserUpdate);
}
message User {
string id = 1;
string name = 2;
string email = 3;
google.protobuf.Timestamp created_at = 4;
}
message GetUserRequest {
string id = 1;
}
message ListUsersRequest {
int32 page_size = 1;
string page_token = 2;
}
message ListUsersResponse {
repeated User users = 1;
string next_page_token = 2;
}
gRPC と REST の使い分け
| 要素 | gRPC | REST |
|---|---|---|
| パフォーマンス | バイナリ、高速 | JSON、人間が読みやすい |
| ストリーミング | 双方向 | SSE/WebSocket による代替手段 |
| 型安全性 | Proto が型を生成 | OpenAPI + コード生成 |
| ブラウザー | gRPC-Web プロキシが必要 | ネイティブ |
| ツール | Protoc、Buf | Swagger、Postman |
| 最適な用途 | サービス間通信、ストリーミング | パブリック API、Web クライアント |
TypeScript 向け tRPC
// server/router.ts
import { router, publicProcedure, protectedProcedure } from './trpc';
import { z } from 'zod';
export const appRouter = router({
user: router({
get: publicProcedure
.input(z.object({ id: z.string() }))
.query(async ({ input }) => {
return db.user.findUnique({ where: { id: input.id } });
}),
create: protectedProcedure
.input(z.object({
name: z.string().min(1),
email: z.string().email(),
}))
.mutation(async ({ input, ctx }) => {
return db.user.create({ data: { ...input, createdBy: ctx.userId } });
}),
}),
});
export type AppRouter = typeof appRouter;
// client.ts - Full type inference, no codegen
const user = trpc.user.get.useQuery({ id: '123' });
const createUser = trpc.user.create.useMutation();
tRPC は、クライアントとサーバーが同じコードベースを共有するモノレポのフルスタック TypeScript アプリに最適です。
Webhook 設計パターン
Webhook ペイロード
{
"id": "evt_abc123",
"type": "order.completed",
"created_at": "2025-01-15T10:30:00Z",
"data": {
"order_id": "ord_456",
"total": 99.99,
"currency": "USD"
}
}
署名の検証
// Sign webhooks with HMAC-SHA256
import crypto from 'crypto';
function signWebhook(payload: string, secret: string): string {
return crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
}
// Verify on receiving end
function verifyWebhook(payload: string, signature: string, secret: string): boolean {
const expected = signWebhook(payload, secret);
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected),
);
}
再試行戦略
Attempt 1: Immediately
Attempt 2: After 1 minute
Attempt 3: After 5 minutes
Attempt 4: After 30 minutes
Attempt 5: After 2 hours
Attempt 6: After 24 hours (final)
Failed webhooks: log, alert, manual retry UI
冪等性
// Include idempotency key in webhook
// Receivers should deduplicate based on event ID
async function handleWebhook(event: WebhookEvent) {
// Check if already processed
const existing = await db.processedEvents.findUnique({
where: { eventId: event.id },
});
if (existing) return { status: 'already_processed' };
// Process and record
await db.$transaction([
processEvent(event),
db.processedEvents.create({ data: { eventId: event.id } }),
]);
}
API バージョニング戦略
| 戦略 | 例 | メリット | デメリット |
|---|---|---|---|
| URL パス | /api/v1/users | 明示的で、ルーティングが簡単です | URL が煩雑になります |
| クエリパラメーター | /api/users?version=1 | オプションのパラメーターです | 見落としやすいです |
| ヘッダー | Accept: application/vnd.api.v1 | URL がすっきりします | 見えにくく、テストが難しくなります |
| コンテントネゴシエーション | Accept: application/json;v=2 | 標準に準拠しています | 実装が複雑です |
推奨: シンプルさを重視して URL パスによるバージョニングを採用します。後方互換性のない変更の場合にのみメジャーバージョンを上げます。同じバージョン内では、追加的で後方互換性のある変更を行います。
コメント
Markdown 対応