Next.js API 路由使用入门

1,750 阅读11分钟

如果觉得这篇文章对你有所帮助,希望能点个赞加个关注

Next.js 是基于 React 的 Web 开发框架,旨在帮助开发者高效的构建生产级应用,支持静态生成和服务器端渲染,并且内置了众多实用功能。

Next.js 采用基于文件的路由系统,并提供 API 路由功能,允许我们在 pages 目录中直接构建 API ,从而无需创建额外的后端项目。这使得 Next.js 实现 RESTful 或 GraphQL API 变得异常简单。

在本文中,我们将学习如何使用 Next.js 的 API 路由系统,并详细解释其核心概念,以及探讨如何在实际应用中如何高效利用这一功能。

Next.js API 路由简介

API(Application Programming Interface,应用程序编程接口)是实现不同应用程序之间相互通信的桥梁,它允许一个应用程序能够访问另一个应用程序的特定功能和数据,从而实现数据交换或功能调用。

在现代 Web 开发中,构建依赖外部数据源提供和存储数据的应用程序时,通常情况下,需要开发两个应用:一个是在浏览器中运行的客户端应用,负责与用户交互;另一个是在服务器上运行的服务器端应用,负责管理数据的请求和存储。这两个应用通过 API 进行通信,实现数据的交换和共享。

然而,Next.js 的 API 路由功能简化了全栈 Web 开发,消除了对独立的服务器端应用的依赖。这一特性允许开发者可以直接在 Next.js 应用中实现对数据库的访问和存储逻辑,就像独立的服务器端应用实现的一样。接下来,我们将深入探讨如何快速上手这一强大的开发方式。

在 Next.js 中创建 API 路由

在 Next.js 中,创建 API 路由的方式与创建页面路由类似,主要区别在于 API 路由文件需放置于 pages 文件夹内的 api 子文件夹中。位于 pages/api 文件夹下的所有文件都会被自动识别为 API 路由。例如,如果 pages/api 中存在一个名为 example.js 的文件,则可以通过发送请求到 /api/example 路径来访问该 API 路由。

让我们看一个例子:

当我们使用 create-next-app 创建了一个 Next.js 项目后,打开项目根目录下的 pages 文件夹,通常会看到一个名为 api 的子文件夹(如果该文件夹不存在,你需要自行创建它)。这个 api 文件夹专门用于存放 Next.js 的 API 路由文件,在这些文件中,我们可以定义服务器端逻辑,以响应来自客户端的请求。作为示例,我们可能会发现一个名为 hello.js 的文件,它展示了如何创建一个简单的 API 路由。这个文件通常包含以下内容:

// pages/api/hello.js
export default function handler(req, res) {
  res.status(200).json({ name: "John Doe" });
}

在浏览器中访问 localhost:3000/api/hello 或通过其他方式向该地址发送 GET 请求,我们将接收到以下 JSON 格式的响应数据:

图片.png

下面是对上述示例代码的解释说明。

每个 API 路由文件都必须导出一个默认函数,用于处理发送到该端点的请求。该函数接受两个参数:

req:http.IncomingMessage 的一个实例,它被用来表示从客户端收到的 HTTP 请求,提供了很多有用的属性,例如 req.bodyreq.query 等,用于解析传入的请求信息。

res:http.ServerResponse 的一个实例,它用于表示从服务器端发送到客户端的 HTTP 响应,提供了很多有用的方法,例如 res.sendres.body 等,用于向客户端发送响应信息。

如果你此前使用过 Express.js ,那么你应该熟悉这些属性和方法。

在我们的示例中,我们导出了一个名为 handler 的默认函数(可以根据需要自由命名),用于处理 HTTP 请求。在此函数中,我们利用 res (服务器响应对象)来设置 HTTP 状态码,并返回 JSON 格式的数据作为响应内容。

默认情况下,不论使用哪种 HTTP 请求方法(如GET、PUT、DELETE等),同一 API 路由都会返回相同的响应。为了根据不同 HTTP 请求方法返回不同的响应,我们可以利用 switch 语句(当然,if/else 结构同样适用,选择哪种方式完全取决于个人偏好)来编写更灵活的处理函数,示例如下:

// pages/api/hello.js
export default function handler(req, res) {
  const requestMethod = req.method;
  const body = JSON.parse(req.body);
  switch (requestMethod) {
    case "POST":
      res
        .status(200)
        .json({ message: `你提交了以下数据:${body}` });

    // 处理其他 HTTP 方法
    default:
      res.status(200).json({ message: "欢迎访问 API 路由!" });
  }
}

下面是另一个示例,演示了如何在应用程序中通过 POST 方法将表单数据发送到给定 API 路由,以便服务器存储数据。

pages/post.js 文件中,负责将表单数据通过 POST 方法发送至 api/post API 路由,代码如下:

// pages/post.js
import { useState } from "react";

const Post = () => {
  const [title, setTitle] = useState("");
  const [post, setPost] = useState("");

  function handleSubmit(e) {
    e.preventDefault();
    const postData = async () => {
      const data = {
        title: title,
        post: post,
      };

      const response = await fetch("/api/post", {
        method: "POST",
        body: JSON.stringify(data),
      });
      return response.json();
    };
    postData().then((data) => {
      alert(data.message);
    });
  }

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label htmlFor="Title">标题</label>
        <input
          id="title"
          type="text"
          value={title}
          onChange={(e) => setTitle(e.target.value)}
        />
      </div>
      <div>
        <label htmlFor="post">正文</label>
        <input
          id="post"
          type="text"
          value={post}
          onChange={(e) => setPost(e.target.value)}
        />
      </div>
      <button type="submit">提交</button>
    </form>
  );
};

export default Post;

pages/api/post.js 文件中,负责接收客户端提交的表单数据,代码如下:

// pages/api/post.js
export default function handler(req, res) {
  const { title, post } = JSON.parse(req.body);

  // 此处将提交数据保存到数据库
  res.status(200).json({ message: "帖子创建成功!" });
}

动态 API 路由

Next.js 的 API 路由同样支持动态路由,这与动态页面路由的实现方式类似。利用动态 API 路由,我们可以根据不同的查询参数,向客户端发送特定的响应。下面是一个示例展示了其用法。

在项目的 /pages/api 文件夹中创建一个名为 trivia 的文件夹,然后在 trivia 文件夹中创建一个名为 [number].js 的文件。此时的文件夹结构如下所示:pages/api/trivia/[number].js

在此示例中,我们将使用 superagent 作为 HTTP 客户端,从外部地址 numbersapi.com/ 获取与路由查询数字相关的一个随机事实,并作为我们的 API 路由的响应内容。

我们可以使用以下命令进行安装:

npm install superagent

接下来,将以下代码添加到 [number].js

// pages/api/trivia/[number].js
const superagent = require("superagent");

export default function handler(req, res) {
  const number = Number(req.query.number);

  if (isNaN(number) || typeof number !== "number") {
    res.status(400).send("无效请求!");
  }
  superagent.get(`http://numbersapi.com/${number}`).then((response) => {
    res.status(200).send(response.text);
  });
}

现在,如果我们访问 http://localhost:3000/api/trivia/34 (你可以使用任意数字构建该URL),将会收到一条根据该数字生成的随机事实的内容。如果使用的是无效数字,我们将收到一个 400 错误请求的响应。

自定义配置

Next.js 的 API 路由系统不仅拥有强大的功能,还支持自定义配置。具体而言,Next.js 允许在每个 API 路由文件中通过导出一个 config 对象来覆盖该路由的默认配置,其中 config 对象的 api 属性应该包含针对 API 路由配置的专属选项。

// pages/api/hello.js
export default function handler(req, res) {
  res.status(200).json({ name: "John Doe" });
}

export const config = {
  api: {
    bodyParser: false, // 默认启用正文解析。若设为false,则禁用此功能,允许你直接以流(stream)或原始体(raw-body)形式访问请求正文。
    responseLimit: false, // 自动启用,默认限制响应正文发送量为4MB。
    externalResolver: true, // 当路由由外部解析器(如Express.js或Connect)处理时,可禁用对未解析请求的警告,默认不启用此功能。
  },
};

你可以在此处阅读更多相关信息,了解其他可用的配置选项。

使用中间件

API 路由的中间件功能对于构建现在 API 系统来说是至关重要的,常被用于执行身份验证、日志记录和请求验证等关键任务中。通过利用中间件,我们能够在请求到达路由处理逻辑之前对其进行预处理,从而增强了路由的安全性和可维护性。

以下是一个在 Next.js 中将中间件应用于 API 路由的示例。在这个示例中,我们将构建一个简单的中间件实例,并展示了如何将其应用于 API 路由中。

  1. 实现一个中间件函数:
// middleware/auth.js
export function authenticate(req, res, next) {
  const token = req.headers.authorization;

  if (token === "你的密钥令牌") {
    next(); // 继续执行下一个中间件或路由处理逻辑
  } else {
    res.status(401).json({ message: "认证失败" });
  }
}
  1. 将中间件应用于 API 路由:
// pages/api/protected.js
import { authenticate } from "../../middleware/auth";

export default function handler(req, res) {
  authenticate(req, res, () => {
    res.status(200).json({ message: "你已通过身份验证!" });
  });
}

在此示例中,authenticate 中间件负责验证请求标头中是否包含有效的认证令牌。如果令牌有效,则继续执行相应的处理程序;如果令牌无效,则直接返回 401 未授权响应。

保护 API 路由

由于暴露的 API 路由极容易受到攻击,并且存在未经授权的访问风险,因此对 API 路由实施保护至关重要。

以下是一些在 Next.js 中实现 API 路由保护的最佳实践:

  1. 启用 HTTPS
  • 原因: HTTPS对客户端发送到服务器的数据进行加密,因此攻击者无法窃听或篡改数据。
  • 方法: 确保生产环境使用 HTTPS 运行。
  1. 对访问速率实施限制
  • 原因: 速率限制是用于防止 API 路由遭受过度请求的有效方法。通过实施速率限制,可以确保每个客户端在特定时间内只能发送有限数量的请求。
  • 方法: 利用中间件并结合使用如 express-rate-limit 这样的第三方库,可以轻松实现对 API 路由访问速率的有效限制。
import rateLimit from "express-rate-limit";

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 分钟
  max: 100, // 将每个 IP 限制为每 windowMs 100 个请求
});

export default function handler(req, res) {
  limiter(req, res, () => {
    // 此处继续 API 处理逻辑
  });
}
  1. 验证输入数据
  • 原因: 通过执行严格的输入验证,可以有效防止注入攻击,确保传到服务器的数据符合预期的格式和类型。
  • 方法: 我们可以利用 joiyup 等库来验证用户的输入数据。
import { object, string } from "yup";

const schema = object({
  name: string().required(),
  email: string().email().required(),
});

export default async function handler(req, res) {
  try {
    await schema.validate(req.body);
    // 此处继续 API 处理逻辑
    res.status(200).json({ message: "数据有效" });
  } catch (error) {
    res.status(400).json({ message: error.message });
  }
}
  1. 对请求进行身份验证和授权
  • 原因: 身份验证确保了请求的发起者是合法用户,而授权则进一步确保了这些合法用户拥有对特定资源的访问权限。
  • 方法: 使用 JWT(JSON Web Tokens)或 OAuth 建立身份验证机制,并验证用户的角色和权限。
import jwt from "jsonwebtoken";

export default function handler(req, res) {
  const token = req.headers.authorization?.split(" ")[1];
  if (!token) {
    return res.status(401).json({ message: "认证失败" });
  }

  try {
    const user = jwt.verify(token, process.env.JWT_SECRET);
    // 在此处查看用户角色和权限
    res.status(200).json({ message: "认证成功", user });
  } catch (error) {
    res.status(403).json({ message: "无效" });
  }
}
  1. 保护敏感信息
  • 原因: 为了防止数据泄露,必须保护敏感信息(例如 API 密钥和密码)。
  • 方法: 通过使用环境变量来存储敏感数据,避免将这些信息直接硬编码在代码中。
export default function handler(req, res) {
  const apiKey = process.env.API_KEY;
  // 安全地使用 API 密钥
  res.status(200).json({ message: "API 密钥是安全的" });
}

严格实施这些最佳实践,能够显著提升我们 Next.js API 路由的安全性,从而有效抵御潜在的安全威胁。

为 API 路由编写测试程序

为 API 路由编写测试程序是软件开发过程中一个非常重要的环节,它有助于确保 API 的稳定性和可靠性,同时促进代码质量的提升。

单元测试

单元测试是确保应用程序各个部分按照预期逻辑运行的重要手段。对于 API 路由而言,这意味着要对路由处理程序进行测试,以确保能够对给定输入产生正确的输出。我们可以借助像 Jest 这样的测试框架来编写单元测试。

下面是一个使用 Jest 对 API 路由进行单元测试的简单示例。

  1. API 路由处理程序:
// pages/api/hello.js
export default function handler(req, res) {
  res.status(200).json({ name: "John Doe" });
}
  1. 单元测试:
// __tests__/api/hello.test.js
import { createMocks } from "node-mocks-http";
import handler from "../../pages/api/hello";

describe("/api/hello API Endpoint", () => {
  test("返回 200 状态码和正确响应", async () => {
    const { req, res } = createMocks();
    await handler(req, res);

    expect(res.statusCode).toBe(200);
    expect(res._getJSONData()).toEqual({ name: "John Doe" });
  });
});

以上代码利用 node-mocks-http 在 API 路由测试程序中创建模拟请求和响应对象,通过响应对象,我们能够断言响应状态码和正文内容是否符合预期。

集成测试

集成测试的目的是验证应用程序各个部分之间是否能无缝协作运行。对于 API 路由而言,就是在测试路由处理程序时,将其置于应用程序的上下文环境中,如初始化数据库连接或连接其他 API 服务等。

下面是一个 API 路由进行集成测试的示例。

  1. API 路由处理程序:
// pages/api/user.js
import { getUser } from "../../lib/user";

export default async function handler(req, res) {
  const user = await getUser(req.query.id);
  if (user) {
    res.status(200).json(user);
  } else {
    res.status(404).json({ message: "用户不存在" });
  }
}
  1. 集成测试:
// __tests__/api/user.test.js
import { createMocks } from "node-mocks-http";
import handler from "../../pages/api/user";
import { getUser } from "../../lib/user";

jest.mock("../../lib/user");

describe("/api/user API Endpoint", () => {
  test("如果用户存在,则应该返回 200 状态码和用户数据", async () => {
    const { req, res } = createMocks({ query: { id: "1" } });
    getUser.mockResolvedValue({ id: "1", name: "John Doe" });

    await handler(req, res);

    expect(res.statusCode).toBe(200);
    expect(res._getJSONData()).toEqual({ id: "1", name: "John Doe" });
  });

  test("如果用户不存在,则应该返回 404 状态码", async () => {
    const { req, res } = createMocks({ query: { id: "2" } });
    getUser.mockResolvedValue(null);

    await handler(req, res);

    expect(res.statusCode).toBe(404);
    expect(res._getJSONData()).toEqual({ message: "用户不存在" });
  });
});

在以上代码中,我们将通过 getUser 函数模拟从数据库中读取用户信息,API 路由将根据用户是否存在返回相应的 HTTP 状态码和响应内容。通过集成测试,我们能够确保该 API 路由的行为始终符合我们的预期。

结语

在本文中,我们详细介绍了 Next.js 中的 API 路由系统,并手把手教你如何轻松在 Next.js 应用中构建 API 。现在,我相信你完全有能力在自己的下一个 Next.js 项目中充分利用 API 路由这一强大功能了!