Skip to content

route_handlers.tsx

文件信息

  • 📄 原文件:03_route_handlers.tsx
  • 🔤 语言:TypeScript (Next.js / React)

本文件介绍 Next.js App Router 中的 Route Handlers。Route Handlers 使用 Web API 的 Request/Response 对象创建自定义请求处理程序,替代 Pages Router 的 API Routes。

完整代码

tsx
/**
 * ============================================================
 *          Next.js Route Handlers (API 路由)
 * ============================================================
 * 本文件介绍 Next.js App Router 中的 Route Handlers。
 *
 * Route Handlers 使用 Web API 的 Request/Response 对象
 * 创建自定义请求处理程序,替代 Pages Router 的 API Routes。
 *
 * 核心概念:
 * - 定义在 route.ts 文件中(不是 page.tsx)
 * - 导出 HTTP 方法函数:GET, POST, PUT, PATCH, DELETE
 * - 使用标准 Web API:Request, Response, Headers
 * ============================================================
 */

import { NextRequest, NextResponse } from 'next/server';
import { cookies, headers } from 'next/headers';
import { revalidateTag } from 'next/cache';

// ============================================================
//               1. Route Handler 基础
// ============================================================

/**
 * 【Route Handler — 路由处理器】
 *
 * 文件约定:app/api/xxx/route.ts → /api/xxx
 * 注意:route.ts 和 page.tsx 不能在同一目录中共存!
 * 支持的方法:GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS
 */

// --- 基本 GET / POST ---
export async function GET() {
    return Response.json({ message: '你好,世界!' });
}

export async function POST(request: Request) {
    const body = await request.json();
    // const post = await db.post.create({ data: body });
    return Response.json({ message: '创建成功', data: body }, { status: 201 });
}

// --- 动态路由 CRUD ---
// app/api/products/[id]/route.ts
async function handleGetProduct(
    request: Request,
    { params }: { params: Promise<{ id: string }> }
) {
    const { id } = await params;
    // const product = await db.product.findUnique({ where: { id } });
    // if (!product) return Response.json({ error: '不存在' }, { status: 404 });
    return Response.json({ id, name: '示例商品', price: 99 });
}

async function handleUpdateProduct(
    request: Request,
    { params }: { params: Promise<{ id: string }> }
) {
    const { id } = await params;
    const body = await request.json();
    // await db.product.update({ where: { id }, data: body });
    return Response.json({ id, ...body });
}

async function handleDeleteProduct(
    request: Request,
    { params }: { params: Promise<{ id: string }> }
) {
    const { id } = await params;
    // await db.product.delete({ where: { id } });
    return new Response(null, { status: 204 });
}


// ============================================================
//               2. 请求处理
// ============================================================

/**
 * 【请求处理 — Request Handling】
 *
 * 使用标准 Web Request 或 NextRequest(扩展版):
 * - nextUrl:解析后的 URL(方便获取 searchParams)
 * - cookies:Cookie 操作方法
 * - geo / ip:地理位置信息(Vercel 部署时可用)
 */

// --- URL 参数 ---
async function handleSearch(request: NextRequest) {
    const searchParams = request.nextUrl.searchParams;
    const query = searchParams.get('q');            // ?q=关键词
    const page = searchParams.get('page') ?? '1';
    const limit = searchParams.get('limit') ?? '10';

    // const results = await db.product.findMany({
    //     where: { name: { contains: query } },
    //     skip: (Number(page) - 1) * Number(limit),
    //     take: Number(limit),
    // });

    return Response.json({ query, page: Number(page), results: [] });
}

// --- 请求头 ---
async function handleWithHeaders(request: NextRequest) {
    // 方式一:从 request 读取
    const authorization = request.headers.get('authorization');
    // 方式二:使用 next/headers
    const headersList = await headers();
    const userAgent = headersList.get('user-agent');
    return Response.json({ authorization, userAgent });
}

// --- Cookies ---
async function handleWithCookies(request: NextRequest) {
    // 方式一:从 NextRequest 读取
    const theme = request.cookies.get('theme')?.value;
    // 方式二:使用 next/headers
    const cookieStore = await cookies();
    const token = cookieStore.get('session-token')?.value;
    return Response.json({ theme, token });
}

// --- 请求体的多种读取方式 ---
async function handleRequestBody(request: Request) {
    // const json = await request.json();         // JSON
    // const form = await request.formData();     // FormData
    // const text = await request.text();         // 纯文本
    // const buf  = await request.arrayBuffer();  // 二进制
    return Response.json({ received: true });
}


// ============================================================
//               3. 响应构建
// ============================================================

/**
 * 【响应构建 — Response Building】
 *
 * - Response.json():标准 JSON 响应
 * - NextResponse.json():扩展版,支持 cookies 设置等
 * - NextResponse.redirect():重定向
 * - ReadableStream:流式响应
 */

// --- NextResponse 设置 Cookie ---
async function jsonWithCookie() {
    const res = NextResponse.json({ data: 'hello' }, { status: 200 });
    res.cookies.set('visited', 'true', {
        httpOnly: true,
        secure: true,
        sameSite: 'lax',
        maxAge: 60 * 60 * 24 * 7,   // 7 天
    });
    return res;
}

// --- 重定向 ---
async function handleRedirect(request: NextRequest) {
    return NextResponse.redirect(new URL('/login', request.url));
}

// --- 流式响应 ---
async function handleStream() {
    const encoder = new TextEncoder();
    const stream = new ReadableStream({
        async start(controller) {
            for (const chunk of ['第一部分\n', '第二部分\n', '第三部分\n']) {
                controller.enqueue(encoder.encode(chunk));
                await new Promise(r => setTimeout(r, 1000));
            }
            controller.close();
        },
    });
    return new Response(stream, {
        headers: { 'Content-Type': 'text/plain; charset=utf-8' },
    });
}

// --- SSE (Server-Sent Events) ---
async function handleSSE() {
    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 Response(stream, {
        headers: { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache' },
    });
}


// ============================================================
//               4. 动态与静态
// ============================================================

/**
 * 【动态与静态 Route Handler】
 *
 * 仅 GET + 不使用动态函数 → 构建时静态缓存
 *
 * 以下情况自动切换为动态:
 * - 使用 Request 对象读取 headers / cookies
 * - 使用 POST / PUT / DELETE 等方法
 * - 使用 cookies()、headers() 等动态函数
 * - 配置 dynamic = 'force-dynamic'
 */

// --- 静态 Route Handler ---
async function handleStaticConfig() {
    return Response.json({
        version: '1.0.0',
        features: ['dark-mode', 'i18n'],
    });
}

// --- 配置选项 ---
// export const dynamic = 'force-dynamic';   // 强制动态
// export const revalidate = 60;              // 每 60 秒重验证
// export const runtime = 'edge';             // Edge Runtime

// --- 带缓存标签 ---
async function handleTaggedGet() {
    const data = await fetch('https://api.example.com/data', {
        next: { tags: ['api-data'] },
    });
    return Response.json(await data.json());
}

// 触发重验证
async function handleRevalidate(request: Request) {
    const body = await request.json();
    if (body.secret !== process.env.REVALIDATION_SECRET) {
        return Response.json({ error: '未授权' }, { status: 401 });
    }
    revalidateTag('api-data');
    return Response.json({ revalidated: true });
}


// ============================================================
//               5. CORS 处理
// ============================================================

/**
 * 【CORS 跨域配置】
 *
 * API 被其他域名调用时需配置 CORS:
 * 1. Route Handler 中手动设置响应头
 * 2. 封装 withCors 工具函数
 * 3. next.config.js 全局配置
 */

const corsHeaders = {
    'Access-Control-Allow-Origin': 'https://example.com',
    'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
    'Access-Control-Allow-Headers': 'Content-Type, Authorization',
    'Access-Control-Max-Age': '86400',
};

// --- OPTIONS 预检 ---
export async function OPTIONS() {
    return new Response(null, { status: 204, headers: corsHeaders });
}

// --- 带 CORS 的响应 ---
async function handleCorsGet() {
    return Response.json({ items: ['a', 'b'] }, { headers: corsHeaders });
}

// --- 封装 CORS 工具函数 ---
function withCors(response: Response, origin?: string): Response {
    const h = new Headers(response.headers);
    h.set('Access-Control-Allow-Origin', origin ?? '*');
    h.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
    h.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
    return new Response(response.body, { status: response.status, headers: h });
}

// --- next.config.js 全局方式 ---
// const nextConfig = {
//     async headers() {
//         return [{
//             source: '/api/:path*',
//             headers: [
//                 { key: 'Access-Control-Allow-Origin', value: '*' },
//                 { key: 'Access-Control-Allow-Methods', value: 'GET,POST,PUT,DELETE' },
//             ],
//         }];
//     },
// };


// ============================================================
//               6. 认证集成
// ============================================================

/**
 * 【认证集成 — Authentication】
 *
 * 常见认证模式:
 * - JWT Token 验证
 * - Session Cookie 检查
 * - 封装认证中间件函数
 * - 与 NextAuth.js / Auth.js 集成
 */

// --- JWT Token 验证 ---
async function handleProtectedApi(request: NextRequest) {
    const authHeader = request.headers.get('authorization');
    if (!authHeader?.startsWith('Bearer ')) {
        return Response.json({ error: '缺少认证令牌' }, { status: 401 });
    }

    const token = authHeader.split(' ')[1];
    try {
        // const payload = await verifyJWT(token);
        const payload = { userId: '123', role: 'admin' };
        return Response.json({ user: payload });
    } catch {
        return Response.json({ error: '令牌无效或已过期' }, { status: 401 });
    }
}

// --- 封装认证中间件 ---
type AuthHandler = (
    request: NextRequest,
    context: { params: Promise<any>; user: any }
) => Promise<Response>;

function withAuth(handler: AuthHandler) {
    return async (request: NextRequest, context: { params: Promise<any> }) => {
        const cookieStore = await cookies();
        const sessionToken = cookieStore.get('session-token')?.value;
        if (!sessionToken) {
            return Response.json({ error: '未登录' }, { status: 401 });
        }
        // const session = await verifySession(sessionToken);
        const session = { userId: '123', role: 'user' };
        return handler(request, { ...context, user: session });
    };
}

// 使用认证中间件
const protectedGet = withAuth(async (request, { user }) => {
    return Response.json({ message: `欢迎,用户 ${user.userId}` });
});

// --- 角色权限控制 ---
function withRole(roles: string[], handler: AuthHandler) {
    return withAuth(async (request, context) => {
        if (!roles.includes(context.user.role)) {
            return Response.json({ error: '权限不足' }, { status: 403 });
        }
        return handler(request, context);
    });
}

const adminOnly = withRole(['admin'], async (request, { user }) => {
    return Response.json({ adminData: '机密内容' });
});


// ============================================================
//               7. 最佳实践
// ============================================================

/**
 * 【Route Handler 最佳实践】
 *
 * ✅ 推荐做法:
 * - 使用 NextRequest/NextResponse 获取扩展功能
 * - 为外部访问的 API 正确配置 CORS
 * - 统一错误响应格式 { error: string, code?: string }
 * - 封装认证逻辑为可复用中间件函数
 * - 对输入参数做验证(Zod / 手动校验)
 * - 合理使用缓存,静态数据默认缓存,动态数据 force-dynamic
 * - 为 webhook 端点验证签名防止伪造
 *
 * ❌ 避免做法:
 * - 避免处理可用 Server Actions 替代的表单提交
 * - 避免将 route.ts 和 page.tsx 放在同一目录
 * - 避免在 Edge Runtime 中使用 Node.js 原生模块
 * - 避免不处理错误导致 500 泄漏堆栈信息
 * - 避免在 GET handler 中修改数据(违反 HTTP 语义)
 * - 避免生产环境 CORS origin 硬编码为 '*'
 *
 * 【路由组织结构推荐】
 *
 *   app/api/
 *   ├── auth/
 *   │   ├── login/route.ts          # POST 登录
 *   │   └── [...nextauth]/route.ts  # NextAuth
 *   ├── products/
 *   │   ├── route.ts                # GET 列表, POST 创建
 *   │   └── [id]/route.ts           # GET/PUT/DELETE
 *   ├── upload/route.ts             # POST 文件上传
 *   └── webhook/stripe/route.ts     # POST 回调
 *
 * 【Route Handler vs Server Action vs Middleware】
 *
 *   特性             │ Route Handler │ Server Action │ Middleware
 *   ────────────────────────────────────────────────────────
 *   位置             │ route.ts      │ 'use server'  │ middleware.ts
 *   HTTP 方法        │ 任意          │ 仅 POST       │ 任意(拦截)
 *   外部客户端调用    │ 是            │ 否            │ 否
 *   返回值           │ Response      │ 可序列化值     │ NextResponse
 *   典型用途         │ RESTful API   │ 数据变更       │ 重定向/认证
 */

💬 讨论

使用 GitHub 账号登录后即可参与讨论

基于 MIT 许可发布