Next.js API 路由
Next.js API 路由让你可以在同一个项目中创建后端 API 端点,无需单独的服务器。本章将详细介绍如何创建和使用 API 路由。
API 路由基础
App Router API 路由
在 Next.js 13+ 的 App Router 中,API 路由使用 route.ts 文件:
typescript
// app/api/hello/route.ts
export async function GET() {
return Response.json({ message: 'Hello, World!' })
}
export async function POST(request: Request) {
const body = await request.json()
return Response.json({ received: body })
}Pages Router API 路由
在传统的 Pages Router 中:
typescript
// pages/api/hello.ts
import type { NextApiRequest, NextApiResponse } from 'next'
type Data = {
message: string
}
export default function handler(
req: NextApiRequest,
res: NextApiResponse<Data>
) {
if (req.method === 'GET') {
res.status(200).json({ message: 'Hello, World!' })
} else {
res.setHeader('Allow', ['GET'])
res.status(405).end(`Method ${req.method} Not Allowed`)
}
}HTTP 方法处理
App Router 方法处理
typescript
// app/api/users/route.ts
import { NextRequest } from 'next/server'
// GET 请求 - 获取用户列表
export async function GET() {
const users = await fetchUsers()
return Response.json(users)
}
// POST 请求 - 创建新用户
export async function POST(request: NextRequest) {
const userData = await request.json()
const newUser = await createUser(userData)
return Response.json(newUser, { status: 201 })
}
// PUT 请求 - 更新用户
export async function PUT(request: NextRequest) {
const userData = await request.json()
const updatedUser = await updateUser(userData)
return Response.json(updatedUser)
}
// DELETE 请求 - 删除用户
export async function DELETE(request: NextRequest) {
const { searchParams } = new URL(request.url)
const id = searchParams.get('id')
if (!id) {
return Response.json({ error: '缺少用户 ID' }, { status: 400 })
}
await deleteUser(id)
return Response.json({ message: '用户已删除' })
}Pages Router 方法处理
typescript
// pages/api/users.ts
import type { NextApiRequest, NextApiResponse } from 'next'
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
switch (req.method) {
case 'GET':
const users = await fetchUsers()
res.status(200).json(users)
break
case 'POST':
const newUser = await createUser(req.body)
res.status(201).json(newUser)
break
case 'PUT':
const updatedUser = await updateUser(req.body)
res.status(200).json(updatedUser)
break
case 'DELETE':
await deleteUser(req.query.id as string)
res.status(200).json({ message: '用户已删除' })
break
default:
res.setHeader('Allow', ['GET', 'POST', 'PUT', 'DELETE'])
res.status(405).end(`Method ${req.method} Not Allowed`)
}
}动态 API 路由
App Router 动态路由
typescript
// app/api/users/[id]/route.ts
import { NextRequest } from 'next/server'
export async function GET(
request: NextRequest,
{ params }: { params: { id: string } }
) {
const user = await fetchUser(params.id)
if (!user) {
return Response.json({ error: '用户未找到' }, { status: 404 })
}
return Response.json(user)
}
export async function PUT(
request: NextRequest,
{ params }: { params: { id: string } }
) {
const userData = await request.json()
const updatedUser = await updateUser(params.id, userData)
return Response.json(updatedUser)
}
export async function DELETE(
request: NextRequest,
{ params }: { params: { id: string } }
) {
await deleteUser(params.id)
return Response.json({ message: '用户已删除' })
}Pages Router 动态路由
typescript
// pages/api/users/[id].ts
import type { NextApiRequest, NextApiResponse } from 'next'
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const { id } = req.query
switch (req.method) {
case 'GET':
const user = await fetchUser(id as string)
if (!user) {
return res.status(404).json({ error: '用户未找到' })
}
res.status(200).json(user)
break
case 'PUT':
const updatedUser = await updateUser(id as string, req.body)
res.status(200).json(updatedUser)
break
case 'DELETE':
await deleteUser(id as string)
res.status(200).json({ message: '用户已删除' })
break
default:
res.setHeader('Allow', ['GET', 'PUT', 'DELETE'])
res.status(405).end(`Method ${req.method} Not Allowed`)
}
}请求和响应处理
处理请求体
typescript
// app/api/posts/route.ts
export async function POST(request: Request) {
try {
// JSON 数据
const data = await request.json()
// 表单数据
// const formData = await request.formData()
// 文本数据
// const text = await request.text()
const post = await createPost(data)
return Response.json(post, { status: 201 })
} catch (error) {
return Response.json(
{ error: '无效的请求数据' },
{ status: 400 }
)
}
}处理查询参数
typescript
// app/api/search/route.ts
import { NextRequest } from 'next/server'
export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url)
const query = searchParams.get('q')
const page = searchParams.get('page') || '1'
const limit = searchParams.get('limit') || '10'
if (!query) {
return Response.json(
{ error: '缺少查询参数' },
{ status: 400 }
)
}
const results = await searchPosts({
query,
page: parseInt(page),
limit: parseInt(limit)
})
return Response.json(results)
}设置响应头
typescript
// app/api/data/route.ts
export async function GET() {
const data = await fetchData()
return Response.json(data, {
status: 200,
headers: {
'Cache-Control': 'public, max-age=3600',
'Content-Type': 'application/json',
'X-Custom-Header': 'custom-value'
}
})
}中间件和认证
认证中间件
typescript
// lib/auth.ts
import jwt from 'jsonwebtoken'
export function verifyToken(token: string) {
try {
return jwt.verify(token, process.env.JWT_SECRET!)
} catch (error) {
return null
}
}
export function extractToken(request: Request): string | null {
const authHeader = request.headers.get('authorization')
if (authHeader && authHeader.startsWith('Bearer ')) {
return authHeader.substring(7)
}
return null
}受保护的 API 路由
typescript
// app/api/protected/route.ts
import { NextRequest } from 'next/server'
import { verifyToken, extractToken } from '@/lib/auth'
export async function GET(request: NextRequest) {
const token = extractToken(request)
if (!token) {
return Response.json(
{ error: '缺少认证令牌' },
{ status: 401 }
)
}
const user = verifyToken(token)
if (!user) {
return Response.json(
{ error: '无效的令牌' },
{ status: 401 }
)
}
// 返回受保护的数据
const data = await fetchProtectedData(user.id)
return Response.json(data)
}文件上传
处理文件上传
typescript
// app/api/upload/route.ts
import { NextRequest } from 'next/server'
import { writeFile } from 'fs/promises'
import path from 'path'
export async function POST(request: NextRequest) {
try {
const formData = await request.formData()
const file = formData.get('file') as File
if (!file) {
return Response.json(
{ error: '没有找到文件' },
{ status: 400 }
)
}
// 验证文件类型
const allowedTypes = ['image/jpeg', 'image/png', 'image/gif']
if (!allowedTypes.includes(file.type)) {
return Response.json(
{ error: '不支持的文件类型' },
{ status: 400 }
)
}
// 验证文件大小 (5MB)
if (file.size > 5 * 1024 * 1024) {
return Response.json(
{ error: '文件太大' },
{ status: 400 }
)
}
const bytes = await file.arrayBuffer()
const buffer = Buffer.from(bytes)
// 生成唯一文件名
const filename = `${Date.now()}-${file.name}`
const filepath = path.join(process.cwd(), 'public/uploads', filename)
await writeFile(filepath, buffer)
return Response.json({
message: '文件上传成功',
filename,
url: `/uploads/${filename}`
})
} catch (error) {
return Response.json(
{ error: '文件上传失败' },
{ status: 500 }
)
}
}数据库集成
使用 Prisma
typescript
// lib/prisma.ts
import { PrismaClient } from '@prisma/client'
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined
}
export const prisma = globalForPrisma.prisma ?? new PrismaClient()
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prismatypescript
// app/api/users/route.ts
import { prisma } from '@/lib/prisma'
export async function GET() {
try {
const users = await prisma.user.findMany({
select: {
id: true,
name: true,
email: true,
createdAt: true
}
})
return Response.json(users)
} catch (error) {
return Response.json(
{ error: '获取用户失败' },
{ status: 500 }
)
}
}
export async function POST(request: Request) {
try {
const { name, email } = await request.json()
const user = await prisma.user.create({
data: { name, email }
})
return Response.json(user, { status: 201 })
} catch (error) {
return Response.json(
{ error: '创建用户失败' },
{ status: 500 }
)
}
}错误处理
统一错误处理
typescript
// lib/api-error.ts
export class ApiError extends Error {
constructor(
public message: string,
public statusCode: number = 500
) {
super(message)
this.name = 'ApiError'
}
}
export function handleApiError(error: unknown) {
if (error instanceof ApiError) {
return Response.json(
{ error: error.message },
{ status: error.statusCode }
)
}
console.error('API Error:', error)
return Response.json(
{ error: '内部服务器错误' },
{ status: 500 }
)
}typescript
// app/api/users/[id]/route.ts
import { ApiError, handleApiError } from '@/lib/api-error'
export async function GET(
request: Request,
{ params }: { params: { id: string } }
) {
try {
const user = await prisma.user.findUnique({
where: { id: params.id }
})
if (!user) {
throw new ApiError('用户未找到', 404)
}
return Response.json(user)
} catch (error) {
return handleApiError(error)
}
}数据验证
使用 Zod 验证
typescript
// lib/validations.ts
import { z } from 'zod'
export const createUserSchema = z.object({
name: z.string().min(1, '姓名不能为空').max(50, '姓名太长'),
email: z.string().email('邮箱格式无效'),
age: z.number().min(0, '年龄不能为负数').max(120, '年龄无效')
})
export type CreateUserInput = z.infer<typeof createUserSchema>typescript
// app/api/users/route.ts
import { createUserSchema } from '@/lib/validations'
export async function POST(request: Request) {
try {
const body = await request.json()
const validatedData = createUserSchema.parse(body)
const user = await prisma.user.create({
data: validatedData
})
return Response.json(user, { status: 201 })
} catch (error) {
if (error instanceof z.ZodError) {
return Response.json(
{ error: '数据验证失败', details: error.errors },
{ status: 400 }
)
}
return handleApiError(error)
}
}缓存策略
响应缓存
typescript
// app/api/posts/route.ts
export async function GET() {
const posts = await fetchPosts()
return Response.json(posts, {
headers: {
'Cache-Control': 'public, s-maxage=60, stale-while-revalidate=300'
}
})
}使用 Next.js 缓存
typescript
// app/api/data/route.ts
import { unstable_cache } from 'next/cache'
const getCachedData = unstable_cache(
async () => {
return await fetchExpensiveData()
},
['expensive-data'],
{ revalidate: 3600 } // 1小时
)
export async function GET() {
const data = await getCachedData()
return Response.json(data)
}测试 API 路由
单元测试
typescript
// __tests__/api/users.test.ts
import { GET, POST } from '@/app/api/users/route'
import { NextRequest } from 'next/server'
describe('/api/users', () => {
it('should return users list', async () => {
const response = await GET()
const data = await response.json()
expect(response.status).toBe(200)
expect(Array.isArray(data)).toBe(true)
})
it('should create a new user', async () => {
const userData = {
name: 'Test User',
email: 'test@example.com'
}
const request = new NextRequest('http://localhost:3000/api/users', {
method: 'POST',
body: JSON.stringify(userData),
headers: { 'Content-Type': 'application/json' }
})
const response = await POST(request)
const data = await response.json()
expect(response.status).toBe(201)
expect(data.name).toBe(userData.name)
expect(data.email).toBe(userData.email)
})
})最佳实践
1. 使用 TypeScript
typescript
interface User {
id: string
name: string
email: string
}
interface ApiResponse<T> {
data?: T
error?: string
message?: string
}2. 统一响应格式
typescript
// lib/api-response.ts
export function successResponse<T>(data: T, message?: string) {
return Response.json({
success: true,
data,
message
})
}
export function errorResponse(error: string, statusCode: number = 400) {
return Response.json({
success: false,
error
}, { status: statusCode })
}3. 环境变量管理
typescript
// lib/env.ts
export const env = {
DATABASE_URL: process.env.DATABASE_URL!,
JWT_SECRET: process.env.JWT_SECRET!,
API_KEY: process.env.API_KEY!,
}
// 验证必需的环境变量
Object.entries(env).forEach(([key, value]) => {
if (!value) {
throw new Error(`Missing required environment variable: ${key}`)
}
})总结
Next.js API 路由提供了强大的后端功能:
- 简单易用 - 基于文件系统的路由
- 全栈开发 - 前后端在同一项目中
- 类型安全 - 完整的 TypeScript 支持
- 性能优化 - 内置缓存和优化
- 部署简单 - 与前端一起部署
通过合理使用 API 路由,你可以构建完整的全栈应用,无需单独的后端服务器。