跳到内容
返回博客

2025年2月28日星期五

使用 Next.js 构建 API

发布者

本指南将介绍如何使用 Next.js 构建 API,包括项目设置、了解 App Router 和路由处理器、处理多种 HTTP 方法、实现动态路由、创建可重用中间件逻辑,以及决定何时搭建专门的 API 层。

1. 开始

1.1 创建 Next.js 应用

如果你从头开始,可以使用以下命令创建一个新的 Next.js 项目:

终端
npx create-next-app@latest --api

注意: --api 标志会自动在你新项目的 app/ 文件夹中包含一个示例 route.ts,演示如何创建 API 端点。

1.2 App Router 与 Pages Router

  • Pages Router:过去,Next.js 使用 pages/api/* 来构建 API。这种方法依赖于 Node.js 的请求/响应对象以及类似 Express 的 API。
  • App Router(默认):Next.js 13 中引入的 App Router 完全采用了 Web 标准的 Request/Response API。现在,你可以在 app/ 目录中的任何位置放置 route.tsroute.js 文件,而不是使用 pages/api/*

为什么要切换? App Router 的“路由处理器”依赖于 Web 平台 Request/Response API,而不是 Node.js 特定的 API。这简化了学习,减少了摩擦,并有助于你在不同工具中重用知识。

2. 为何(以及何时)使用 Next.js 构建 API

  1. 面向多个客户端的公共 API

    • 你可以构建一个公共 API,供你的 Next.js Web 应用、独立的移动应用或任何第三方服务使用。例如,你可以在 React 网站和 React Native 移动应用中都从 /api/users 获取数据。
  2. 现有后端代理

    • 有时你希望将外部 微服务 隐藏或整合到单个端点后面。Next.js 路由处理器可以充当代理或中间层,连接到另一个现有后端。例如,你可以拦截请求、处理身份验证、转换数据,然后将请求传递给上游 API。
  3. Webhooks 和集成

    • 如果你接收外部回调或 Webhooks(例如来自 Stripe、GitHub、Twilio),你可以使用路由处理器来处理它们。
  4. 自定义身份验证

    • 如果你需要会话、令牌或其他身份验证逻辑,可以在 Next.js API 层中存储 cookies、读取头部并响应适当的数据。

注意: 如果你只需要为自己的 Next.js 应用进行服务器端数据获取(并且不需要外部共享数据),那么服务器组件可能足以在渲染期间直接获取数据——不需要单独的 API 层。

3. 使用路由处理器创建 API

3.1 基本文件设置

在 App Router (app/) 中,创建一个表示你的路由的文件夹,并在其中创建一个 route.ts 文件。

例如,要在 /api/users 创建一个端点:

app
└── api
    └── users
        └── route.ts

3.2 单文件中的多个 HTTP 方法

与 Pages Router API 路由(只有一个默认导出)不同,你可以从同一个文件导出表示不同 HTTP 方法的多个函数。

app/api/users/route.ts
export async function GET(request: Request) {
  // For example, fetch data from your DB here
  const users = [
    { id: 1, name: 'Alice' },
    { id: 2, name: 'Bob' }
  ];
  return new Response(JSON.stringify(users), {
    status: 200,
    headers: { 'Content-Type': 'application/json' }
  });
}
 
export async function POST(request: Request) {
  // Parse the request body
  const body = await request.json();
  const { name } = body;
 
  // e.g. Insert new user into your DB
  const newUser = { id: Date.now(), name };
 
  return new Response(JSON.stringify(newUser), {
    status: 201,
    headers: { 'Content-Type': 'application/json' }
  });
}

现在,向 /api/users 发送 GET 请求会返回你的用户列表,而向同一个 URL 发送 POST 请求则会插入一个新用户。

4. 使用 Web API

4.1 直接使用 Request 和 Response

默认情况下,你的路由处理器方法(GETPOST 等)接收一个标准的 Request 对象,并且你必须返回一个标准的 Response 对象。

4.2 查询参数

app/api/search/route.ts
import { NextRequest } from 'next/server';
 
export function GET(request: NextRequest) {
  const searchParams = request.nextUrl.searchParams;
  const query = searchParams.get('query'); // e.g. `/api/search?query=hello`
 
  return new Response(
    JSON.stringify({ result: `You searched for: ${query}` }),
    {
      headers: { 'Content-Type': 'application/json' },
    },
  );
}

4.3 头部和 Cookies

app/api/auth/route.ts
import { NextRequest } from 'next/server';
import { cookies, headers } from 'next/headers';
 
export async function GET(request: NextRequest) {
  // 1. Using 'next/headers' helpers
  const cookieStore = await cookies();
  const token = cookieStore.get('token');
 
  const headersList = await headers();
  const referer = headersList.get('referer');
 
  // 2. Using the standard Web APIs
  const userAgent = request.headers.get('user-agent');
 
  return new Response(JSON.stringify({ token, referer, userAgent }), {
    headers: { 'Content-Type': 'application/json' },
  });
}

cookies()headers() 函数在您计划在 Next.js 中的其他服务器端代码中重用共享逻辑时会很有帮助。您会注意到 Next.js 还提供了 NextRequestNextResponse,它们扩展了基本的 Web API。

5. 动态路由

要创建动态路径(例如 /api/users/:id),请在您的文件夹结构中使用 动态段

app
└── api
    └── users
        └── [id]
            └── route.ts

此文件对应于像 /api/users/123 这样的 URL,其中 123 被捕获为参数。

app/api/users/[id]/route.ts
import { NextRequest } from 'next/server';
 
export async function GET(
  request: NextRequest,
  { params }: { params: Promise<{ id: string }> },
) {
  const id = (await params).id;
  // e.g. Query a database for user with ID `id`
  return new Response(JSON.stringify({ id, name: `User ${id}` }), {
    status: 200,
    headers: { 'Content-Type': 'application/json' },
  });
}
 
export async function DELETE(
  request: NextRequest,
  { params }: { params: Promise<{ id: string }> },
) {
  const id = (await params).id;
  // e.g. Delete user with ID `id` in DB
  return new Response(null, { status: 204 });
}

在这里,params.id 为您提供了动态段。

6. 将 Next.js 用作代理或转发层

一个常见场景是代理现有后端服务。您可以在将请求发送到远程服务器或后端之前,对请求进行身份验证、处理日志记录或转换数据。

app/api/external/route.ts
import { NextRequest } from 'next/server';
 
export async function GET(request: NextRequest) {
  const response = await fetch('https://example.com/api/data', {
    // Optional: forward some headers, add auth tokens, etc.
    headers: { Authorization: `Bearer ${process.env.API_TOKEN}` },
  });
 
  // Transform or forward the response
  const data = await response.json();
  const transformed = { ...data, source: 'proxied-through-nextjs' };
 
  return new Response(JSON.stringify(transformed), {
    headers: { 'Content-Type': 'application/json' },
  });
}

现在你的客户端只需要调用 /api/external,Next.js 会处理其余部分。这有时也称为“前端后端”或 BFF。

7. 构建共享“中间件”逻辑

如果你想将相同的逻辑(例如身份验证检查、日志记录)应用于多个路由处理器,你可以创建可重用函数来包装你的处理器。

lib/with-auth.ts
import { NextRequest } from 'next/server';
 
type Handler = (req: NextRequest, context?: any) => Promise<Response>;
 
export function withAuth(handler: Handler): Handler {
  return async (req, context) => {
    const token = req.cookies.get('token')?.value;
    if (!token) {
      return new Response(JSON.stringify({ error: 'Unauthorized' }), {
        status: 401,
        headers: { 'Content-Type': 'application/json' },
      });
    }
 
    // If authenticated, call the original handler
    return handler(req, context);
  };
}

然后在你的路由处理器中

app/api/secret/route.ts
import { NextRequest } from 'next/server';
import { withAuth } from '@/lib/with-auth';
 
async function secretGET(request: NextRequest) {
  return new Response(JSON.stringify({ secret: 'Here be dragons' }), {
    headers: { 'Content-Type': 'application/json' },
  });
}
 
export const GET = withAuth(secretGET);

8. 部署和“SPA 模式”考量

8.1 标准 Node.js 部署

使用 next start 的标准 Next.js 服务器部署允许您使用路由处理器、服务器组件、中间件等功能——同时利用动态的、请求时信息。

无需额外配置。有关更多详细信息,请参阅部署

8.2 SPA/静态导出

Next.js 还支持将您的整个网站输出为静态单页应用程序 (SPA)

您可以通过设置以下内容来启用此功能:

next.config.ts
import type { NextConfig } from 'next';
 
const nextConfig: NextConfig = {
  output: 'export',
};
 
export default nextConfig;

静态导出模式下,Next.js 将生成纯静态 HTML、CSS 和 JS。您无法运行服务器端代码(例如 API 端点)。如果您仍然需要 API,则必须单独托管它(例如,独立的 Node.js 服务器)。

注意

  • 如果 GET 路由处理器不依赖于动态请求数据,它们可以静态导出。它们会成为您的输出文件夹中的静态文件。
  • 所有其他服务器功能(动态请求、重写 cookies 等)在纯 SPA 导出中不支持

8.3 在 Vercel 上部署 API

如果您正在将 Next.js 应用程序部署到 Vercel,我们提供了一份部署 API 的指南。这包括其他 Vercel 功能,例如通过 Vercel 防火墙进行的程序化速率限制。Vercel 还提供了Cron Jobs,这通常是 API 方法所需要的。

9. 何时跳过创建 API 端点

通过 App Router 的 React Server Components,您可以在服务器上直接获取数据,而无需暴露公共端点。

app/users/page.tsx
// (Server Component)
export default async function UsersPage() {
  // This fetch runs on the server (no client-side code needed here)
  const res = await fetch('https://api.example.com/users');
  const data = await res.json();
 
  return (
    <main>
      <h1>Users</h1>
      <ul>
        {data.map((user: any) => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </main>
  );
}

如果您的数据仅在您的 Next.js 应用内部使用,您可能根本不需要公共 API。

10. 整合所有内容

  1. 创建一个新的 Next.js 项目npx create-next-app@latest --api
  2. app/ 目录中添加路由处理器(例如,app/api/users/route.ts)。
  3. 在同一个文件中导出 HTTP 方法GETPOSTPUTDELETE 等)。
  4. 使用 Web 标准 APIRequest 对象交互并返回 Response
  5. 如果您需要其他客户端使用您的数据,或者需要代理后端服务,则构建一个公共 API
  6. 从客户端获取您新的 API 路由(例如,在客户端组件中或使用 fetch('/api/...'))。
  7. 或者,如果服务器组件可以直接获取数据,则完全跳过创建 API
  8. 为身份验证或其他重复逻辑添加共享“中间件”模式(例如,withAuth())。
  9. 部署到支持 Node.js 的环境以使用服务器功能,或者如果您只需要静态 SPA,则进行静态导出

结论

使用 Next.js 的 App Router路由处理器 为您提供了一种灵活、现代的方式来直接构建拥抱 Web 平台 的 API。您可以:

  • 创建完整的公共 API,供 Web、移动或第三方客户端共享。
  • 代理并自定义对现有外部服务的调用。
  • 实现可重用的“中间件”层,用于身份验证、日志记录或任何重复逻辑。
  • 使用 [id] 段文件夹结构动态路由请求。

常见问题

Server Actions 是什么?

您可以将服务器动作 (Server Actions) 视为可以从客户端调用的自动生成的 POST API 路由。

它们专为变异操作而设计,例如创建、更新或删除数据。您可以像调用普通的 JavaScript 函数一样调用服务器动作,而不是对已定义的 API 路由进行明确的 fetch 请求。

虽然仍然存在网络请求,但您无需明确管理它。URL 路径是自动生成的并加密的,因此您无法在浏览器中手动访问像 /api/users 这样的路由。

如果您计划同时使用服务器动作和公开公共 API,我们建议将核心逻辑移动到数据访问层,并从服务器动作和 API 路由中调用相同的逻辑。

我可以在路由处理器中使用 TypeScript 吗?

是的,您可以在路由处理器中使用 TypeScript。例如,在您的 route 文件中定义 RequestResponse 类型。

了解更多关于Next.js 和 TypeScript 的信息。

身份验证的最佳实践是什么?

在我们的身份验证文档中了解更多信息。