API Design について
API Design は、Next.js アプリの API 開発を支援するスキルです。具体的なワークフローと例を通じて、ルート設計、検証、セキュリティなどを扱います。
主な機能
- 01RESTful ルートハンドラーのパターン
- 02Zod によるリクエスト検証
- 03認証ミドルウェア
- 04レート制限とページネーション
- 05統一されたエラーと HTTP ステータスコード
活用シーン
- 01リソースベースの API エンドポイント設計
- 02リクエスト本文とパラメーターの検証
- 03認証によるルート保護
- 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 ルートを作成します
手順:
- リソースベースの URL 構造を計画します
- 各 HTTP メソッドのルートハンドラーを作成します
- リクエストの検証を実装します
- 適切なエラー処理を追加します
- 一貫したレスポンス形式を返します
実装:
// 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 のパフォーマンスを最適化するとき
コメント
Markdown 対応