route-handlers について
route-handlersは、Next.jsでAPIエンドポイントを開発するためのリファレンススキルです。リクエストとレスポンス、HTTPメソッド、ストリーミング、キャッシュ、エラー処理を扱います。
主な機能
- 01route.tsファイルによるエンドポイント定義
- 02HTTPメソッドとリクエストデータの処理
- 03JSON、リダイレクト、ストリーミングレスポンスの返却
- 04ヘッダー、Cookie、CORS、キャッシュの設定
活用シーン
- 01CRUD APIエンドポイントの作成
- 02クエリパラメーターと動的ルート値の読み取り
- 03テキストやLLMレスポンスのストリーミング
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ davepoon/ buildwithclaude --skill route-handlersインストール後、Claude Code で/route-handlersを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
examples/crud-api.mdreferences/http-methods.mdstreaming-responses.md詳細
Next.js Route Handlers
概要
Route Handlers を使用すると、Web Request API と Response API を使って API エンドポイントを作成できます。app ディレクトリ内の route.ts ファイルで定義します。
基本構造
ファイルの規約
ルートハンドラーには route.ts(または route.js)を使用します。
app/
├── api/
│ ├── users/
│ │ └── route.ts # /api/users
│ └── posts/
│ ├── route.ts # /api/posts
│ └── [id]/
│ └── route.ts # /api/posts/:id
HTTP メソッド
HTTP メソッド名の関数をエクスポートします。
// app/api/users/route.ts
import { NextResponse } from 'next/server'
export async function GET() {
const users = await db.user.findMany()
return NextResponse.json(users)
}
export async function POST(request: Request) {
const body = await request.json()
const user = await db.user.create({ data: body })
return NextResponse.json(user, { status: 201 })
}
サポートされているメソッド:GET、POST、PUT、PATCH、DELETE、HEAD、OPTIONS
リクエストの処理
リクエストボディの読み取り
export async function POST(request: Request) {
// JSON body
const json = await request.json()
// Form data
const formData = await request.formData()
const name = formData.get('name')
// Text body
const text = await request.text()
return NextResponse.json({ received: true })
}
URL パラメーター
動的なルートパラメーター:
// app/api/posts/[id]/route.ts
interface RouteContext {
params: Promise<{ id: string }>
}
export async function GET(
request: Request,
context: RouteContext
) {
const { id } = await context.params
const post = await db.post.findUnique({ where: { id } })
if (!post) {
return NextResponse.json(
{ error: 'Not found' },
{ status: 404 }
)
}
return NextResponse.json(post)
}
クエリパラメーター
export async function GET(request: Request) {
const { searchParams } = new URL(request.url)
const page = searchParams.get('page') ?? '1'
const limit = searchParams.get('limit') ?? '10'
const posts = await db.post.findMany({
skip: (parseInt(page) - 1) * parseInt(limit),
take: parseInt(limit),
})
return NextResponse.json(posts)
}
リクエストヘッダー
export async function GET(request: Request) {
const authHeader = request.headers.get('authorization')
if (!authHeader?.startsWith('Bearer ')) {
return NextResponse.json(
{ error: 'Unauthorized' },
{ status: 401 }
)
}
const token = authHeader.split(' ')[1]
// Validate token...
return NextResponse.json({ authenticated: true })
}
レスポンスの処理
JSON レスポンス
import { NextResponse } from 'next/server'
export async function GET() {
return NextResponse.json(
{ message: 'Hello' },
{ status: 200 }
)
}
ヘッダーの設定
export async function GET() {
return NextResponse.json(
{ data: 'value' },
{
headers: {
'Cache-Control': 'max-age=3600',
'X-Custom-Header': 'custom-value',
},
}
)
}
Cookie の設定
import { cookies } from 'next/headers'
export async function POST(request: Request) {
const cookieStore = await cookies()
// Set cookie
cookieStore.set('session', 'abc123', {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
maxAge: 60 * 60 * 24 * 7, // 1 week
})
return NextResponse.json({ success: true })
}
リダイレクト
import { redirect } from 'next/navigation'
import { NextResponse } from 'next/server'
export async function GET() {
// Option 1: redirect function (throws)
redirect('/login')
// Option 2: NextResponse.redirect
return NextResponse.redirect(new URL('/login', request.url))
}
ストリーミングレスポンス
テキストストリーミング
export async function GET() {
const encoder = new TextEncoder()
const stream = new ReadableStream({
async start(controller) {
for (let i = 0; i < 10; i++) {
controller.enqueue(encoder.encode(`data: ${i}\n\n`))
await new Promise(resolve => setTimeout(resolve, 100))
}
controller.close()
},
})
return new Response(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
},
})
}
AI/LLM のストリーミング
export async function POST(request: Request) {
const { prompt } = await request.json()
const response = await openai.chat.completions.create({
model: 'gpt-4',
messages: [{ role: 'user', content: prompt }],
stream: true,
})
const stream = new ReadableStream({
async start(controller) {
for await (const chunk of response) {
const text = chunk.choices[0]?.delta?.content || ''
controller.enqueue(new TextEncoder().encode(text))
}
controller.close()
},
})
return new Response(stream, {
headers: { 'Content-Type': 'text/plain' },
})
}
CORS の設定
export async function OPTIONS() {
return new Response(null, {
status: 204,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
},
})
}
export async function GET() {
return NextResponse.json(
{ data: 'value' },
{
headers: {
'Access-Control-Allow-Origin': '*',
},
}
)
}
キャッシュ
静的(GET のデフォルト)
// Cached by default
export async function GET() {
const data = await fetch('https://api.example.com/data')
return NextResponse.json(await data.json())
}
キャッシュを無効にする
export const dynamic = 'force-dynamic'
export async function GET() {
// Always fresh
}
// Or use cookies/headers (auto opts out)
import { cookies } from 'next/headers'
export async function GET() {
const cookieStore = await cookies()
// Now dynamic
}
エラー処理
export async function GET(request: Request) {
try {
const data = await riskyOperation()
return NextResponse.json(data)
} catch (error) {
console.error('API Error:', error)
if (error instanceof ValidationError) {
return NextResponse.json(
{ error: error.message },
{ status: 400 }
)
}
return NextResponse.json(
{ error: 'Internal Server Error' },
{ status: 500 }
)
}
}
リソース
詳細なパターンについては、以下を参照してください。
references/http-methods.md- HTTP メソッドの完全ガイドreferences/streaming-responses.md- 高度なストリーミングパターンexamples/crud-api.md- CRUD API の完全な例
コメント
Markdown 対応