验证数据
当您在服务器端接收到数据时,必须对其进行验证。所谓验证,是指接收的数据结构必须与预期结构相匹配。这很重要,因为您无法信任来自未知来源的数据,比如用户或外部 API。
readBody
这样的工具提供接口并不是验证。您必须在使用数据之前进行验证。验证辅助工具
H3 提供了一些辅助工具来帮助您处理数据验证。您将能够验证:
- 使用
getValidatedQuery
验证查询参数 - 使用
getValidatedRouterParams
验证路由参数 - 使用
readValidatedBody
验证请求体
H3 不提供任何验证库,但它支持来自Standard-Schema 兼容库的 schema,比如:Zod、Valibot、ArkType 等……(所有兼容库请查看它们的官方仓库)。如果您想使用一个不兼容 Standard-Schema 的验证库,您仍然可以使用,但必须使用该库自身提供的解析函数(请参阅下面的安全解析部分)。
下面让我们看看如何使用 Zod 和 Valibot 进行数据验证。
验证路由参数
您可以使用 getValidatedRouterParams
来验证路由参数并获取结果,替代 getRouterParams
:
import { getValidatedRouterParams } from "h3";
import * as z from "zod";
import * as v from "valibot";
// 使用 Zod 示例
const contentSchema = z.object({
topic: z.string().min(1),
uuid: z.string().uuid(),
});
// 使用 Valibot 示例
const contentSchema = v.object({
topic: v.pipe(v.string(), v.nonEmpty()),
uuid: v.pipe(v.string(), v.uuid()),
});
router.use(
// 您必须使用一个路由以使用参数
"/content/:topic/:uuid",
async (event) => {
const params = await getValidatedRouterParams(event, contentSchema);
return `您正在查找 topic 为 "${params.topic}" 且 uuid 为 "${params.uuid}" 的内容。`;
},
);
如果您向该事件处理程序发送一个有效请求,例如 /content/posts/123e4567-e89b-12d3-a456-426614174000
,您将得到如下响应:
您正在查找 topic 为 "posts" 且 uuid 为 "123e4567-e89b-12d3-a456-426614174000" 的内容。
如果您发送了无效请求且验证失败,H3 将抛出 400 Validation Error
错误。在错误数据中,您会找到验证错误信息,可以在客户端使用它来向用户展示友好的错误提示。
验证查询参数
您可以使用 getValidatedQuery
来验证查询参数并获取结果,替代 getQuery
:
import { getValidatedQuery } from "h3";
import * as z from "zod";
import * as v from "valibot";
// 使用 Zod 示例
const stringToNumber = z
.string()
.regex(/^\d+$/, "必须是数字字符串")
.transform(Number);
const paginationSchema = z.object({
page: stringToNumber.optional().default(1),
size: stringToNumber.optional().default(10),
});
// 使用 Valibot 示例
const stringToNumber = v.pipe(
v.string(),
v.regex(/^\d+$/, "必须是数字字符串"),
v.transform(Number),
);
const paginationSchema = v.object({
page: v.optional(stringToNumber, 1),
size: v.optional(stringToNumber, 10),
});
app.use(async (event) => {
const query = await getValidatedQuery(event, paginationSchema);
return `您当前在第 ${query.page} 页,每页显示 ${query.size} 条内容。`;
});
如您所见,相较于 getValidatedRouterParams
示例,我们可以利用验证库对传入数据进行转换,例如这里把数字的字符串形式转换为真正的数字,这在内容分页等场景中非常有用。
如果您向该事件处理程序发送一个有效请求,比如 /?page=2&size=20
,您将得到如下响应:
您当前在第 2 页,每页显示 20 条内容。
如果您发送了无效请求且验证失败,H3 将抛出 400 Validation Error
错误。在错误数据中,您会找到验证错误信息,可以在客户端使用它来向用户展示友好的错误提示。
验证请求体
您可以使用 readValidatedBody
来验证请求体并获取结果,替代 readBody
:
import { readValidatedBody } from "h3";
import { z } from "zod";
import * as v from "valibot";
// 使用 Zod 示例
const userSchema = z.object({
name: z.string().min(3).max(20),
age: z.number({ coerce: true }).positive().int(),
});
// 使用 Valibot 示例
const userSchema = v.object({
name: v.pipe(v.string(), v.minLength(3), v.maxLength(20)),
age: v.pipe(v.number(), v.integer(), v.minValue(0)),
});
app.use(async (event) => {
const body = await readValidatedBody(event, userSchema);
return `你好 ${body.name}!你今年 ${body.age} 岁。`;
});
如果您发送一个带有如下 JSON 请求体的有效 POST 请求:
{
"name": "John",
"age": 42
}
您将得到如下响应:
你好 John!你今年 42 岁。
如果您发送了无效请求且验证失败,H3 将抛出 400 Validation Error
错误。在错误数据中,您会找到验证错误信息,可以在客户端使用它来向用户展示友好的错误提示。
安全解析
默认情况下,如果直接将 schema 作为第二个参数传给任何验证辅助工具(getValidatedRouterParams
、getValidatedQuery
和 readValidatedBody
),当验证失败时会抛出 400 Validation Error
错误。但在某些情况下,您可能想自己处理验证错误。这时,您应该根据所使用的验证库,传入其提供的实际安全验证函数作为第二个参数。
回到第一个示例中 getValidatedRouterParams
,对于 Zod 会是这样:
import { getValidatedRouterParams } from "h3";
import { z } from "zod/v4";
const contentSchema = z.object({
topic: z.string().min(1),
uuid: z.string().uuid(),
});
router.use("/content/:topic/:uuid", async (event) => {
const params = await getValidatedRouterParams(event, contentSchema.safeParse);
if (!params.success) {
// 处理验证错误
return `验证失败:\n${z.prettifyError(params.error)}`;
}
return `您正在查找 topic 为 "${params.data.topic}" 且 uuid 为 "${params.data.uuid}" 的内容。`;
});
而对于 Valibot 则是这样:
import { getValidatedRouterParams } from "h3";
import * as v from "valibot";
const contentSchema = v.object({
topic: v.pipe(v.string(), v.nonEmpty()),
uuid: v.pipe(v.string(), v.uuid()),
});
router.use("/content/:topic/:uuid", async (event) => {
const params = await getValidatedRouterParams(
event,
v.safeParser(contentSchema),
);
if (!params.success) {
// 处理验证错误
return `验证失败:\n${v.summarize(params.issues)}`;
}
return `您正在查找 topic 为 "${params.output.topic}" 且 uuid 为 "${params.output.uuid}" 的内容。`;
});