如果觉得这篇文章对你有所帮助,希望能点个赞加个关注
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 格式的响应数据:
下面是对上述示例代码的解释说明。
每个 API 路由文件都必须导出一个默认函数,用于处理发送到该端点的请求。该函数接受两个参数:
req:http.IncomingMessage 的一个实例,它被用来表示从客户端收到的 HTTP 请求,提供了很多有用的属性,例如 req.body 、req.query 等,用于解析传入的请求信息。
res:http.ServerResponse 的一个实例,它用于表示从服务器端发送到客户端的 HTTP 响应,提供了很多有用的方法,例如 res.send 、res.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 路由中。
- 实现一个中间件函数:
// middleware/auth.js
export function authenticate(req, res, next) {
const token = req.headers.authorization;
if (token === "你的密钥令牌") {
next(); // 继续执行下一个中间件或路由处理逻辑
} else {
res.status(401).json({ message: "认证失败" });
}
}
- 将中间件应用于 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 路由保护的最佳实践:
- 启用 HTTPS
- 原因: HTTPS对客户端发送到服务器的数据进行加密,因此攻击者无法窃听或篡改数据。
- 方法: 确保生产环境使用 HTTPS 运行。
- 对访问速率实施限制
- 原因: 速率限制是用于防止 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 处理逻辑
});
}
- 验证输入数据
- 原因: 通过执行严格的输入验证,可以有效防止注入攻击,确保传到服务器的数据符合预期的格式和类型。
- 方法: 我们可以利用
joi或yup等库来验证用户的输入数据。
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 });
}
}
- 对请求进行身份验证和授权
- 原因: 身份验证确保了请求的发起者是合法用户,而授权则进一步确保了这些合法用户拥有对特定资源的访问权限。
- 方法: 使用 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: "无效" });
}
}
- 保护敏感信息
- 原因: 为了防止数据泄露,必须保护敏感信息(例如 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 路由进行单元测试的简单示例。
- API 路由处理程序:
// pages/api/hello.js
export default function handler(req, res) {
res.status(200).json({ name: "John Doe" });
}
- 单元测试:
// __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 路由进行集成测试的示例。
- 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: "用户不存在" });
}
}
- 集成测试:
// __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 路由这一强大功能了!