Skip to content

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 = prisma
typescript
// 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 路由,你可以构建完整的全栈应用,无需单独的后端服务器。

本站内容仅供学习和研究使用。