🙋 需要创建 REST API 或处理 Webhook?Route Handlers 让你在 App Router 中轻松构建 API 端点。
基础用法#
创建 Route Handler#
app/└── api/ └── hello/ └── route.ts # /api/hello// Next.js 15.ximport { NextResponse } from 'next/server'
export async function GET() { return NextResponse.json({ message: 'Hello, World!' })}HTTP 方法#
import { NextRequest, NextResponse } from 'next/server'
// GET /api/postsexport async function GET() { const posts = await db.post.findMany() return NextResponse.json(posts)}
// POST /api/postsexport async function POST(request: NextRequest) { const data = await request.json() const post = await db.post.create({ data }) return NextResponse.json(post, { status: 201 })}import { NextRequest, NextResponse } from 'next/server'
// GET /api/posts/:idexport async function GET( request: NextRequest, { params }: { params: Promise<{ id: string }> }) { 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)}
// PUT /api/posts/:idexport async function PUT( request: NextRequest, { params }: { params: Promise<{ id: string }> }) { const { id } = await params const data = await request.json()
const post = await db.post.update({ where: { id }, data, })
return NextResponse.json(post)}
// DELETE /api/posts/:idexport async function DELETE( request: NextRequest, { params }: { params: Promise<{ id: string }> }) { const { id } = await params await db.post.delete({ where: { id } })
return new NextResponse(null, { status: 204 })}支持的 HTTP 方法:GET、POST、PUT、PATCH、DELETE、HEAD、OPTIONS
请求处理#
获取请求数据#
import { NextRequest, NextResponse } from 'next/server'
export async function POST(request: NextRequest) { // JSON 数据 const json = await request.json()
// FormData const formData = await request.formData() const name = formData.get('name')
// 文本 const text = await request.text()
// ArrayBuffer const buffer = await request.arrayBuffer()
return NextResponse.json({ received: true })}URL 参数#
import { NextRequest, NextResponse } from 'next/server'
// GET /api/search?q=keyword&page=1export async function GET(request: NextRequest) { const searchParams = request.nextUrl.searchParams const query = searchParams.get('q') const page = parseInt(searchParams.get('page') || '1')
const results = await db.post.findMany({ where: { title: { contains: query || '' }, }, skip: (page - 1) * 10, take: 10, })
return NextResponse.json({ results, page, query, })}请求头#
import { NextRequest, NextResponse } from 'next/server'import { headers } from 'next/headers'
export async function GET(request: NextRequest) { // 方式一:从 request 获取 const authHeader = request.headers.get('authorization')
// 方式二:使用 headers() 函数 const headersList = await headers() const userAgent = headersList.get('user-agent')
if (!authHeader?.startsWith('Bearer ')) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) }
const token = authHeader.split(' ')[1] // 验证 token...
return NextResponse.json({ authenticated: true })}Cookies#
import { NextRequest, NextResponse } from 'next/server'import { cookies } from 'next/headers'
export async function GET(request: NextRequest) { // 方式一:从 request 获取 const token = request.cookies.get('token')?.value
// 方式二:使用 cookies() 函数 const cookieStore = await cookies() const session = cookieStore.get('session')
return NextResponse.json({ token, session: session?.value })}
export async function POST(request: NextRequest) { const cookieStore = await cookies()
// 设置 cookie cookieStore.set('token', 'abc123', { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'lax', maxAge: 60 * 60 * 24 * 7, // 7 天 })
return NextResponse.json({ success: true })}
export async function DELETE() { const cookieStore = await cookies()
// 删除 cookie cookieStore.delete('token')
return NextResponse.json({ success: true })}响应处理#
JSON 响应#
import { NextResponse } from 'next/server'
export async function GET() { // 基本 JSON 响应 return NextResponse.json({ name: 'Next.js' })
// 带状态码 return NextResponse.json({ error: 'Not found' }, { status: 404 })
// 带自定义头 return NextResponse.json( { data: 'value' }, { status: 200, headers: { 'X-Custom-Header': 'custom-value', }, } )}重定向#
import { NextRequest, NextResponse } from 'next/server'import { redirect } from 'next/navigation'
export async function GET(request: NextRequest) { // 方式一:NextResponse.redirect return NextResponse.redirect(new URL('/dashboard', request.url))
// 方式二:redirect 函数 redirect('/dashboard')}设置响应头#
import { NextRequest, NextResponse } from 'next/server'
export async function GET() { const response = NextResponse.json({ data: 'value' })
// 设置 CORS 头 response.headers.set('Access-Control-Allow-Origin', '*') response.headers.set('Access-Control-Allow-Methods', 'GET, POST, OPTIONS') response.headers.set('Access-Control-Allow-Headers', 'Content-Type')
return response}
// 处理 OPTIONS 预检请求export async function OPTIONS() { return new NextResponse(null, { status: 204, headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type, Authorization', }, })}流式响应#
文本流#
import { NextResponse } from 'next/server'
export async function GET() { const encoder = new TextEncoder()
const stream = new ReadableStream({ async start(controller) { const messages = ['Hello', ' ', 'World', '!']
for (const message of messages) { controller.enqueue(encoder.encode(message)) await new Promise((resolve) => setTimeout(resolve, 500)) }
controller.close() }, })
return new NextResponse(stream, { headers: { 'Content-Type': 'text/plain', 'Transfer-Encoding': 'chunked', }, })}Server-Sent Events#
import { NextResponse } from 'next/server'
export async function GET() { const encoder = new TextEncoder()
const stream = new ReadableStream({ async start(controller) { let count = 0
const interval = setInterval(() => { count++ const data = JSON.stringify({ count, time: new Date().toISOString() }) controller.enqueue(encoder.encode(`data: ${data}\n\n`))
if (count >= 10) { clearInterval(interval) controller.close() } }, 1000) }, })
return new NextResponse(stream, { headers: { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive', }, })}客户端使用:
'use client'
import { useEffect, useState } from 'react'
export function EventStream() { const [events, setEvents] = useState<string[]>([])
useEffect(() => { const eventSource = new EventSource('/api/events')
eventSource.onmessage = (event) => { setEvents((prev) => [...prev, event.data]) }
eventSource.onerror = () => { eventSource.close() }
return () => eventSource.close() }, [])
return ( <ul> {events.map((event, i) => ( <li key={i}>{event}</li> ))} </ul> )}AI 流式响应#
import { NextRequest } from 'next/server'
export async function POST(request: NextRequest) { const { messages } = await request.json()
const response = await fetch('https://api.openai.com/v1/chat/completions', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`, }, body: JSON.stringify({ model: 'gpt-4', messages, stream: true, }), })
// 直接转发流 return new Response(response.body, { headers: { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', }, })}文件处理#
文件上传#
import { NextRequest, NextResponse } from 'next/server'import { writeFile } from 'fs/promises'import { join } from 'path'
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 uploaded' }, { status: 400 }) }
const bytes = await file.arrayBuffer() const buffer = Buffer.from(bytes)
const filename = `${Date.now()}-${file.name}` const path = join(process.cwd(), 'public', 'uploads', filename)
await writeFile(path, buffer)
return NextResponse.json({ url: `/uploads/${filename}`, size: file.size, type: file.type, })}文件下载#
import { NextRequest, NextResponse } from 'next/server'import { readFile } from 'fs/promises'import { join } from 'path'
export async function GET( request: NextRequest, { params }: { params: Promise<{ filename: string }> }) { const { filename } = await params const path = join(process.cwd(), 'files', filename)
try { const file = await readFile(path)
return new NextResponse(file, { headers: { 'Content-Type': 'application/octet-stream', 'Content-Disposition': `attachment; filename="${filename}"`, }, }) } catch { return NextResponse.json({ error: 'File not found' }, { status: 404 }) }}缓存与重验证#
默认缓存行为#
import { NextResponse } from 'next/server'
// GET 请求默认会被缓存(如果没有使用动态函数)export async function GET() { return NextResponse.json({ time: new Date().toISOString() })}禁用缓存#
import { NextResponse } from 'next/server'
// 方式一:导出 dynamic 配置export const dynamic = 'force-dynamic'
export async function GET() { return NextResponse.json({ time: new Date().toISOString() })}// 方式二:设置 revalidateexport const revalidate = 0
// 方式三:使用动态函数import { headers } from 'next/headers'
export async function GET() { const headersList = await headers() // 触发动态渲染 return NextResponse.json({ time: new Date().toISOString() })}定时重验证#
import { NextResponse } from 'next/server'
// 每 60 秒重新验证export const revalidate = 60
export async function GET() { const news = await fetch('https://api.example.com/news').then((r) => r.json()) return NextResponse.json(news)}认证与授权#
Bearer Token 认证#
import { NextRequest, NextResponse } from 'next/server'import { verifyToken } from '@/lib/auth'
export async function GET(request: NextRequest) { const authHeader = request.headers.get('authorization')
if (!authHeader?.startsWith('Bearer ')) { return NextResponse.json( { error: 'Missing authorization header' }, { status: 401 } ) }
const token = authHeader.split(' ')[1]
try { const payload = await verifyToken(token) return NextResponse.json({ user: payload }) } catch { return NextResponse.json({ error: 'Invalid token' }, { status: 401 }) }}API Key 认证#
import { NextRequest, NextResponse } from 'next/server'
const API_KEYS = new Set(process.env.API_KEYS?.split(',') || [])
function validateApiKey(request: NextRequest) { const apiKey = request.headers.get('x-api-key') return apiKey && API_KEYS.has(apiKey)}
export async function GET(request: NextRequest) { if (!validateApiKey(request)) { return NextResponse.json({ error: 'Invalid API key' }, { status: 403 }) }
// 处理请求... return NextResponse.json({ data: 'protected' })}Webhook 处理#
GitHub Webhook#
import { NextRequest, NextResponse } from 'next/server'import crypto from 'crypto'
function verifySignature(payload: string, signature: string) { const secret = process.env.GITHUB_WEBHOOK_SECRET! const expectedSignature = `sha256=${crypto .createHmac('sha256', secret) .update(payload) .digest('hex')}`
return crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(expectedSignature) )}
export async function POST(request: NextRequest) { const payload = await request.text() const signature = request.headers.get('x-hub-signature-256')
if (!signature || !verifySignature(payload, signature)) { return NextResponse.json({ error: 'Invalid signature' }, { status: 401 }) }
const event = request.headers.get('x-github-event') const data = JSON.parse(payload)
switch (event) { case 'push': console.log('Push event:', data.ref) // 处理推送事件 break case 'pull_request': console.log('PR event:', data.action) // 处理 PR 事件 break }
return NextResponse.json({ received: true })}Stripe Webhook#
import { NextRequest, NextResponse } from 'next/server'import Stripe from 'stripe'
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!)const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET!
export async function POST(request: NextRequest) { const payload = await request.text() const signature = request.headers.get('stripe-signature')!
let event: Stripe.Event
try { event = stripe.webhooks.constructEvent(payload, signature, webhookSecret) } catch (err) { return NextResponse.json({ error: 'Invalid signature' }, { status: 400 }) }
switch (event.type) { case 'checkout.session.completed': const session = event.data.object as Stripe.Checkout.Session await handleCheckoutComplete(session) break case 'invoice.paid': const invoice = event.data.object as Stripe.Invoice await handleInvoicePaid(invoice) break }
return NextResponse.json({ received: true })}
async function handleCheckoutComplete(session: Stripe.Checkout.Session) { // 处理支付完成}
async function handleInvoicePaid(invoice: Stripe.Invoice) { // 处理发票支付}实战示例#
RESTful API#
import { NextRequest, NextResponse } from 'next/server'import { z } from 'zod'import { db } from '@/lib/db'
const CreateUserSchema = z.object({ name: z.string().min(1).max(100), email: z.string().email(),})
export async function GET(request: NextRequest) { const { searchParams } = request.nextUrl const page = parseInt(searchParams.get('page') || '1') const limit = parseInt(searchParams.get('limit') || '10')
const [users, total] = await Promise.all([ db.user.findMany({ skip: (page - 1) * limit, take: limit, select: { id: true, name: true, email: true, createdAt: true }, }), db.user.count(), ])
return NextResponse.json({ data: users, meta: { page, limit, total, pages: Math.ceil(total / limit), }, })}
export async function POST(request: NextRequest) { const body = await request.json() const result = CreateUserSchema.safeParse(body)
if (!result.success) { return NextResponse.json( { errors: result.error.flatten().fieldErrors }, { status: 400 } ) }
const existing = await db.user.findUnique({ where: { email: result.data.email }, })
if (existing) { return NextResponse.json({ error: 'Email already exists' }, { status: 409 }) }
const user = await db.user.create({ data: result.data, })
return NextResponse.json(user, { status: 201 })}import { NextRequest, NextResponse } from 'next/server'import { z } from 'zod'import { db } from '@/lib/db'
const UpdateUserSchema = z.object({ name: z.string().min(1).max(100).optional(), email: z.string().email().optional(),})
export async function GET( request: NextRequest, { params }: { params: Promise<{ id: string }> }) { const { id } = await params const user = await db.user.findUnique({ where: { id }, select: { id: true, name: true, email: true, createdAt: true }, })
if (!user) { return NextResponse.json({ error: 'User not found' }, { status: 404 }) }
return NextResponse.json(user)}
export async function PATCH( request: NextRequest, { params }: { params: Promise<{ id: string }> }) { const { id } = await params const body = await request.json() const result = UpdateUserSchema.safeParse(body)
if (!result.success) { return NextResponse.json( { errors: result.error.flatten().fieldErrors }, { status: 400 } ) }
try { const user = await db.user.update({ where: { id }, data: result.data, }) return NextResponse.json(user) } catch { return NextResponse.json({ error: 'User not found' }, { status: 404 }) }}
export async function DELETE( request: NextRequest, { params }: { params: Promise<{ id: string }> }) { const { id } = await params
try { await db.user.delete({ where: { id } }) return new NextResponse(null, { status: 204 }) } catch { return NextResponse.json({ error: 'User not found' }, { status: 404 }) }}常见问题#
🤔 Q: Route Handlers 和 Server Actions 怎么选?
- Route Handlers:创建 REST API、处理 Webhook、第三方集成
- Server Actions:表单处理、数据变更、与 UI 紧密结合
🤔 Q: 如何处理跨域请求?
import { NextResponse } from 'next/server'import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) { const response = NextResponse.next()
if (request.nextUrl.pathname.startsWith('/api/')) { response.headers.set('Access-Control-Allow-Origin', '*') response.headers.set('Access-Control-Allow-Methods', 'GET, POST, OPTIONS') }
return response}🤔 Q: 如何限制请求频率?
使用中间件或第三方库如 @upstash/ratelimit 实现速率限制。
下一篇将介绍数据库集成,学习如何在 Next.js 中使用 Prisma 和 Drizzle。
-EOF-