8.1 Route Handlers 概述
在 App Router 中,API 端点通过 route.ts(或 route.js)文件定义,称为 Route Handlers。与 Pages Router 的 pages/api/ 不同,Route Handlers 使用标准的 Web API(Request、Response),不依赖 Node.js 特定的 req/res,因此可以运行在 Edge Runtime。
Route Handlers 支持所有 HTTP 方法:GET、POST、PUT、PATCH、DELETE、HEAD、OPTIONS。每个方法对应文件中导出的同名函数。
Route Handler vs Server Action对于需要从前端触发的数据变更操作,优先使用 Server Actions(更简单,内置 CSRF 保护)。Route Handlers 更适合:需要对外提供的 REST API、Webhook 回调、需要自定义 Response headers 的场景、以及与第三方服务集成。
8.2 创建 Route Handler
// app/api/posts/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { auth } from '@/auth'
// GET /api/posts — 获取文章列表
export async function GET(request: NextRequest) {
// 读取 URL 查询参数
const searchParams = request.nextUrl.searchParams
const page = Number(searchParams.get('page') ?? 1)
const limit = Number(searchParams.get('limit') ?? 10)
const posts = await db.post.findMany({
where: { published: true },
skip: (page - 1) * limit,
take: limit,
orderBy: { createdAt: 'desc' },
select: { id: true, title: true, slug: true }
})
// 返回 JSON 响应
return NextResponse.json({ posts, page, limit })
}
// POST /api/posts — 创建文章(需要认证)
export async function POST(request: NextRequest) {
const session = await auth()
if (!session?.user) {
return NextResponse.json(
{ error: 'Unauthorized' },
{ status: 401 }
)
}
const body = await request.json()
const { title, content } = body
if (!title) {
return NextResponse.json(
{ error: 'Title is required' },
{ status: 400 }
)
}
const post = await db.post.create({
data: { title, content, authorId: session.user.id }
})
return NextResponse.json(post, { status: 201 })
}
TS
8.3 动态 Route Handlers
与页面路由相同,Route Handlers 也支持动态参数 [id]。参数通过第二个参数的 params 传入。
// app/api/posts/[id]/route.ts
interface RouteContext {
params: Promise<{ id: string }>
}
// GET /api/posts/:id
export async function GET(
request: NextRequest,
{ params }: RouteContext
) {
const { id } = await params
const post = await db.post.findUnique({ where: { id } })
if (!post) {
return NextResponse.json({ error: 'Not found' }, { status: 404 })
}
return NextResponse.json(post)
}
// PATCH /api/posts/:id
export async function PATCH(
request: NextRequest,
{ params }: RouteContext
) {
const session = await auth()
if (!session?.user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
const { id } = await params
const body = await request.json()
const updated = await db.post.update({
where: { id, authorId: session.user.id }, // 确保只能修改自己的文章
data: body,
})
return NextResponse.json(updated)
}
// DELETE /api/posts/:id
export async function DELETE(
request: NextRequest,
{ params }: RouteContext
) {
const session = await auth()
if (!session?.user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
const { id } = await params
await db.post.delete({ where: { id, authorId: session.user.id } })
return new Response(null, { status: 204 })
}
TS
8.4 Webhook 处理
Webhook 是第三方服务(Stripe、GitHub、Clerk 等)主动推送事件到你的 API 的机制。处理 Webhook 时需要注意:验证签名、使用原始 Body(不能解析 JSON)、快速响应(200/204),耗时操作异步处理。
// app/api/webhooks/stripe/route.ts
import Stripe from 'stripe'
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!)
export async function POST(request: Request) {
// 获取原始 Body(Stripe 签名验证需要)
const body = await request.text()
const signature = request.headers.get('stripe-signature')!
let event: Stripe.Event
try {
event = stripe.webhooks.constructEvent(
body,
signature,
process.env.STRIPE_WEBHOOK_SECRET!
)
} catch (err) {
return new Response('Invalid signature', { status: 400 })
}
// 处理事件(异步,不阻塞响应)
switch (event.type) {
case 'checkout.session.completed':
await handleCheckout(event.data.object)
break
case 'customer.subscription.deleted':
await handleCancellation(event.data.object)
break
}
return new Response('OK', { status: 200 })
}
TS
8.5 middleware.ts 深度解析
middleware.ts 在 Edge Runtime 上运行,在请求到达任何路由(页面或 API)之前执行。它的执行时机决定了其用途:认证检查、A/B 测试、国际化重定向、请求日志、速率限制等。
由于运行在 Edge Runtime,middleware 不能使用 Node.js 专有 API(如 fs、path),也不能直接访问数据库。如果需要数据库操作,应在页面/API 中进行,middleware 只做轻量级的权限判断。
// middleware.ts
import { NextRequest, NextResponse } from 'next/server'
export function middleware(request: NextRequest) {
const { pathname, searchParams } = request.nextUrl
// 1. 国际化:根据 Accept-Language 重定向
const locale = request.headers.get('accept-language')?.[0] ?? 'en'
if (pathname === '/') {
return NextResponse.redirect(new URL(`/${locale}`, request.url))
}
// 2. 克隆请求并添加自定义 Header
const requestHeaders = new Headers(request.headers)
requestHeaders.set('x-pathname', pathname)
const response = NextResponse.next({
request: { headers: requestHeaders },
})
// 3. 在响应中设置 Cookie
response.cookies.set('visited', 'true', {
httpOnly: true,
sameSite: 'lax',
maxAge: 60 * 60 * 24 * 7, // 7天
})
return response
}
// 精确控制 middleware 匹配的路径
export const config = {
matcher: [
// 匹配所有路径,排除 Next.js 内部和静态文件
'/((?!_next/static|_next/image|favicon.ico).*)',
],
}
TS
8.6 Edge Runtime vs Node.js Runtime
Next.js 支持两种运行时,可以在路由段级别声明:
| 特性 | Node.js Runtime(默认) | Edge Runtime |
|---|---|---|
| 冷启动时间 | ~300ms | <1ms |
| Node.js API | 完整支持 | 不支持 |
| 数据库访问 | 直接连接 | 需要 HTTP API 或连接池 |
| Bundle 大小限制 | 无限制 | 4MB |
| 适合场景 | 复杂业务逻辑、数据库操作 | 认证、重定向、A/B测试 |
| 部署位置 | 服务器 | 全球边缘节点 |
// 在路由段中声明使用 Edge Runtime
export const runtime = 'edge'
export async function GET(request: Request) {
// 这个 Route Handler 将在 Edge 上运行
return new Response('Hello from Edge!')
}
TS
CORS 配置如果 API 需要跨域访问,在 Route Handler 的响应中添加 CORS 头:Access-Control-Allow-Origin: *(或指定域名)。也可以在 next.config.ts 的 headers() 函数中全局配置,避免在每个路由重复设置。