mcpField
A
API Design

API Design

github.com/jmsktm開発1067 回インストール41 週間前に更新
リポジトリ

API Design について

API Design は、Next.js アプリの API 開発を支援するスキルです。具体的なワークフローと例を通じて、ルート設計、検証、セキュリティなどを扱います。

主な機能

  1. 01RESTful ルートハンドラーのパターン
  2. 02Zod によるリクエスト検証
  3. 03認証ミドルウェア
  4. 04レート制限とページネーション
  5. 05統一されたエラーと HTTP ステータスコード

活用シーン

  1. 01リソースベースの API エンドポイント設計
  2. 02リクエスト本文とパラメーターの検証
  3. 03認証によるルート保護
  4. 04ページネーションとレート制限の追加

インストール

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

$npx skills add https://github.com/jmsktm/claude-settings --skill api-design

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

詳細

API 設計と統合のスキル

Next.js アプリケーションで堅牢かつスケーラブルな API を設計、実装するための包括的なガイドです。RESTful エンドポイントの設計から、リクエストの検証、エラー処理、認証、レート制限まで、このスキルでは本番環境に対応した API 開発のあらゆる側面を扱います。

直感的に使えて、ドキュメントが整備され、業界のベストプラクティスに従う API を構築します。適切な HTTP セマンティクス、一貫したエラーレスポンス、型安全なリクエスト/レスポンス処理を実装します。

コアワークフロー

ワークフロー 1:RESTful ルートハンドラー

目的: REST の規約に従って、整理された API ルートを作成します

手順:

  1. リソースベースの URL 構造を計画します
  2. 各 HTTP メソッドのルートハンドラーを作成します
  3. リクエストの検証を実装します
  4. 適切なエラー処理を追加します
  5. 一貫したレスポンス形式を返します

実装:

// app/api/users/route.ts
import { NextRequest, NextResponse } from 'next/server'

export async function GET(request: NextRequest) {
  try {
    const users = await db.users.findMany()
    return NextResponse.json({ users })
  } catch (error) {
    return NextResponse.json(
      { error: 'Failed to fetch users' },
      { status: 500 }
    )
  }
}

export async function POST(request: NextRequest) {
  try {
    const body = await request.json()
    const user = await db.users.create({ data: body })
    return NextResponse.json({ user }, { status: 201 })
  } catch (error) {
    return NextResponse.json(
      { error: 'Failed to create user' },
      { status: 400 }
    )
  }
}

// app/api/users/[id]/route.ts
export async function GET(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  const user = await db.users.findUnique({ where: { id: params.id } })

  if (!user) {
    return NextResponse.json(
      { error: 'User not found' },
      { status: 404 }
    )
  }

  return NextResponse.json({ user })
}

export async function PATCH(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  const body = await request.json()
  const user = await db.users.update({
    where: { id: params.id },
    data: body
  })

  return NextResponse.json({ user })
}

export async function DELETE(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  await db.users.delete({ where: { id: params.id } })
  return new NextResponse(null, { status: 204 })
}
ワークフロー 2:Zod によるリクエスト検証

目的: 詳細なエラーメッセージを伴う、型安全なリクエスト検証を行います

実装:

import { z } from 'zod'
import { NextRequest, NextResponse } from 'next/server'

const userSchema = z.object({
  name: z.string().min(2).max(100),
  email: z.string().email(),
  age: z.number().int().min(18).optional()
})

export async function POST(request: NextRequest) {
  try {
    const body = await request.json()
    const validated = userSchema.parse(body)

    const user = await db.users.create({ data: validated })
    return NextResponse.json({ user }, { status: 201 })
  } catch (error) {
    if (error instanceof z.ZodError) {
      return NextResponse.json(
        { error: 'Validation failed', issues: error.issues },
        { status: 400 }
      )
    }
    return NextResponse.json(
      { error: 'Internal server error' },
      { status: 500 }
    )
  }
}
ワークフロー 3:認証ミドルウェア

目的: 認証チェックで API ルートを保護します

実装:

// lib/auth-middleware.ts
import { NextRequest, NextResponse } from 'next/server'

export async function withAuth(
  handler: (req: NextRequest, context: any) => Promise<NextResponse>
) {
  return async (req: NextRequest, context: any) => {
    const token = req.headers.get('authorization')?.replace('Bearer ', '')

    if (!token) {
      return NextResponse.json(
        { error: 'Unauthorized' },
        { status: 401 }
      )
    }

    try {
      const user = await verifyToken(token)
      req.user = user
      return handler(req, context)
    } catch (error) {
      return NextResponse.json(
        { error: 'Invalid token' },
        { status: 401 }
      )
    }
  }
}

// app/api/protected/route.ts
export const GET = withAuth(async (request: NextRequest) => {
  const user = request.user
  return NextResponse.json({ user })
})
ワークフロー 4:レート制限

目的: API を不正利用から保護します

実装:

// lib/rate-limit.ts
const rateLimitMap = new Map<string, { count: number; reset: number }>()

export function rateLimit(limit = 100, window = 60000) {
  return async (request: NextRequest) => {
    const ip = request.ip || 'anonymous'
    const now = Date.now()
    const record = rateLimitMap.get(ip)

    if (!record || now > record.reset) {
      rateLimitMap.set(ip, { count: 1, reset: now + window })
      return null
    }

    if (record.count >= limit) {
      return NextResponse.json(
        { error: 'Too many requests' },
        { status: 429 }
      )
    }

    record.count++
    return null
  }
}

// Usage in route
export async function GET(request: NextRequest) {
  const limitResponse = await rateLimit(100, 60000)(request)
  if (limitResponse) return limitResponse

  // Handle request...
}
ワークフロー 5:ページネーションのパターン

目的: 大規模なデータセットを効率よく処理します

実装:

type PaginatedResponse<T> = {
  data: T[]
  pagination: {
    page: number
    limit: number
    total: number
    totalPages: number
    hasNext: boolean
    hasPrev: boolean
  }
}

export async function GET(request: NextRequest) {
  const searchParams = request.nextUrl.searchParams
  const page = parseInt(searchParams.get('page') || '1')
  const limit = parseInt(searchParams.get('limit') || '10')
  const search = searchParams.get('search') || ''

  const skip = (page - 1) * limit

  const [users, total] = await Promise.all([
    db.users.findMany({
      where: { name: { contains: search } },
      skip,
      take: limit
    }),
    db.users.count({ where: { name: { contains: search } } })
  ])

  const totalPages = Math.ceil(total / limit)

  return NextResponse.json({
    data: users,
    pagination: {
      page,
      limit,
      total,
      totalPages,
      hasNext: page < totalPages,
      hasPrev: page > 1
    }
  })
}

クイックリファレンス

アクションHTTP メソッドURL パターン
リソース一覧の取得GET/api/resources
単一リソースの取得GET/api/resources/:id
作成POST/api/resources
更新PATCH/PUT/api/resources/:id
削除DELETE/api/resources/:id
アクションPOST/api/resources/:id/action

HTTP ステータスコード

成功コード
  • 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 Entity - 検証に失敗
  • 429 Too Many Requests - レート制限を超過
サーバーエラーコード
  • 500 Internal Server Error - 予期しないエラー
  • 503 Service Unavailable - 一時的に利用不可

エラーレスポンス形式

type ErrorResponse = {
  error: string              // Human-readable message
  code?: string             // Machine-readable code
  details?: Record<string, any>  // Additional context
  timestamp?: string
}

// Usage
return NextResponse.json(
  {
    error: 'Validation failed',
    code: 'VALIDATION_ERROR',
    details: { field: 'email', reason: 'Invalid format' },
    timestamp: new Date().toISOString()
  },
  { status: 400 }
)

CORS の設定

// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  const response = NextResponse.next()

  response.headers.set('Access-Control-Allow-Origin', '*')
  response.headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
  response.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization')

  if (request.method === 'OPTIONS') {
    return new NextResponse(null, { status: 200, headers: response.headers })
  }

  return response
}

export const config = {
  matcher: '/api/:path*'
}

ベストプラクティス

  • HTTP セマンティクスを使用する: 正しいステータスコードを使用します(作成には 201、削除には 204)
  • 一貫したレスポンス形式: 常に { data } または { error, code } を返します
  • すべてを検証する: リクエスト本文、クエリ、パラメーターには Zod を使用します
  • 適切にエラーを処理する: 内部エラーをクライアントに決して公開しません
  • API をドキュメント化する: ドキュメントには OpenAPI/Swagger を使用します
  • API にバージョンを付ける: 互換性を破る変更には /api/v1/ を使用します
  • リストをページネーションする: リストエンドポイントでは必ずページネーションを行います
  • CORS を使用する: クライアント側からのリクエストに適切な設定を行います
  • リクエストをログに記録する: デバッグ用にすべての API リクエストを記録します
  • レート制限を適用する: 不正利用を防ぎます

よく使われるパターン

ヘルスチェックエンドポイント
// app/api/health/route.ts
export async function GET() {
  return NextResponse.json({
    status: 'ok',
    timestamp: new Date().toISOString(),
    version: process.env.APP_VERSION
  })
}
ファイルのアップロード
export async function POST(request: NextRequest) {
  const formData = await request.formData()
  const file = formData.get('file') as File

  if (!file) {
    return NextResponse.json(
      { error: 'No file provided' },
      { status: 400 }
    )
  }

  const allowedTypes = ['image/jpeg', 'image/png', 'image/webp']
  if (!allowedTypes.includes(file.type)) {
    return NextResponse.json(
      { error: 'Invalid file type' },
      { status: 400 }
    )
  }

  const url = await uploadFile(file)
  return NextResponse.json({ url }, { status: 201 })
}
Webhook ハンドラー
export async function POST(request: NextRequest) {
  const signature = request.headers.get('x-webhook-signature')
  const body = await request.text()

  const isValid = verifyWebhookSignature(body, signature)
  if (!isValid) {
    return NextResponse.json(
      { error: 'Invalid signature' },
      { status: 401 }
    )
  }

  const event = JSON.parse(body)
  await processWebhookEvent(event)

  return NextResponse.json({ received: true })
}

依存関係

# Validation
npm install zod

# Rate limiting
npm install @upstash/redis @upstash/ratelimit

# API documentation
npm install swagger-ui-react swagger-jsdoc

エラー処理

  • 400 Bad Request: 入力が無効、検証エラー
  • 401 Unauthorized: 認証情報がない、または無効
  • 403 Forbidden: 認証は有効だが、権限が不足
  • 404 Not Found: リソースが存在しない
  • 409 Conflict: リソースがすでに存在する、または状態が競合している
  • 422 Unprocessable: 構文は有効だが、意味上のエラーがある
  • 429 Too Many Requests: レート制限を超過
  • 500 Internal Error: サーバー側の障害

パフォーマンスのヒント

  • クエリ対象のフィールドにデータベースインデックスを使用します
  • 大規模なデータセットにはカーソルベースのページネーションを実装します
  • Redis または SWR を使って、頻繁な読み取りをキャッシュします
  • データベースのコネクションプーリングを使用します
  • フィールド選択によってペイロードサイズを最小限に抑えます
  • gzip でレスポンスを圧縮します

このスキルを使用するタイミング

次の場合にこのスキルを呼び出します:

  • 新しい API エンドポイントを作成するとき
  • リクエストの検証を実装するとき
  • API アーキテクチャを設計するとき
  • 認証/認可を追加するとき
  • ファイルのアップロードを処理するとき
  • Webhook を実装するとき
  • CORS を設定するとき
  • レート制限を追加するとき
  • API エラーをデバッグするとき
  • API のパフォーマンスを最適化するとき

コメント