验证数据

¥Validate Data

在处理数据之前,请确保数据有效且安全。

¥Ensure that your data are valid and safe before processing them.

当你在服务器上接收数据时,必须对其进行验证。我们所说的验证是指接收数据的形状必须与预期形状匹配。这很重要,因为你不能信任来自未知来源(例如用户或外部 API)的数据。

¥When you receive data on your server, you must validate them. By validate, we mean that the shape of the received data must match the expected shape. It's important because you can't trust the data coming from unknown sources, like a user or an external API.

请勿使用类型泛型进行验证。提供像 readBody 这样的实用程序的接口并非验证。你必须在使用数据之前验证数据。¥!WARNING Do not use type generics as a validation. Providing an interface to a utility like readBody is not a validation. You must validate the data before using it.

验证实用程序

¥Utilities for Validation

H3 提供了一些实用程序来帮助你处理数据验证。你将能够验证:

¥H3 provide some utilities to help you to handle data validation. You will be able to validate:

  • 查询部分包含 getValidatedQuery
  • 参数部分包含 getValidatedRouterParams
  • 主体部分包含 readValidatedBody

H3 不提供任何验证库,但它支持来自标准模式兼容库的模式,例如:ZodValibotArkType 等……(所有兼容库请勾选 其官方存储库)。如果你想使用与 Standard-Schema 不兼容的验证库,你仍然可以使用它,但你必须使用该库本身提供的解析函数(请参阅下面的 安全解析 部分)。

¥H3 doesn't provide any validation library but it does support schemas coming from a Standard-Schema compatible one, like: Zod, Valibot, ArkType, etc... (for all compatible libraries please check their official repository). If you want to use a validation library that is not compatible with Standard-Schema, you can still use it, but you will have to use parsing functions provided by the library itself (refer to the Safe Parsing section below).

H3 与运行时无关。这意味着你可以在 任何运行时 中使用它。但某些验证库并非与所有运行时兼容。¥!WARNING H3 is runtime agnostic. This means that you can use it in any runtime. But some validation libraries are not compatible with all runtimes.

让我们看看如何使用 ZodValibot 验证数据。

¥Let's see how to validate data with Zod and Valibot.

验证参数

¥Validate Params

你可以使用 getValidatedRouterParams 验证参数并获取结果,以替代 getRouterParams

¥You can use getValidatedRouterParams to validate params and get the result, as a replacement of getRouterParams:

import { getValidatedRouterParams } from "h3";
import * as z from "zod";
import * as v from "valibot";

// Example with Zod
const contentSchema = z.object({
  topic: z.string().min(1),
  uuid: z.string().uuid(),
});
// Example with Valibot
const contentSchema = v.object({
  topic: v.pipe(v.string(), v.nonEmpty()),
  uuid: v.pipe(v.string(), v.uuid()),
});

router.use(
  // You must use a router to use params
  "/content/:topic/:uuid",
  async (event) => {
    const params = await getValidatedRouterParams(event, contentSchema);
    return `You are looking for content with topic "${params.topic}" and uuid "${params.uuid}".`;
  },
);

如果你向此事件处理程序发送一个类似 /content/posts/123e4567-e89b-12d3-a456-426614174000 的有效请求,你将收到如下响应:

¥If you send a valid request like /content/posts/123e4567-e89b-12d3-a456-426614174000 to this event handler, you will get a response like this:

You are looking for content with topic "posts" and uuid "123e4567-e89b-12d3-a456-426614174000".

如果你发送了一个无效请求且验证失败,H3 将抛出 400 Validation Error 错误。在错误数据中,你将找到可在客户端上使用的验证错误,以便向用户显示友好的错误消息。

¥If you send an invalid request and the validation fails, H3 will throw a 400 Validation Error error. In the data of the error, you will find the validation errors you can use on your client to display a nice error message to your user.

验证查询

¥Validate Query

你可以使用 getValidatedQuery 验证查询并获取结果,以替代 getQuery

¥You can use getValidatedQuery to validate query and get the result, as a replacement of getQuery:

import { getValidatedQuery } from "h3";
import * as z from "zod";
import * as v from "valibot";

// Example with Zod
const stringToNumber = z
  .string()
  .regex(/^\d+$/, "Must be a number string")
  .transform(Number);
const paginationSchema = z.object({
  page: stringToNumber.optional().default(1),
  size: stringToNumber.optional().default(10),
});

// Example with Valibot
const stringToNumber = v.pipe(
  v.string(),
  v.regex(/^\d+$/, "Must be a number string"),
  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 `You are on page ${query.page} with ${query.size} items per page.`;
});

你可能已经注意到,与 getValidatedRouterParams 示例相比,我们可以利用验证库来转换传入的数据。在本例中,我们将数字的字符串表示形式转换为实数,这对于内容分页等功能非常有用。

¥As you may have noticed, compared to the getValidatedRouterParams example, we can leverage validation libraries to transform the incoming data. In this case, we transform the string representation of a number into a real number, which is useful for things like content pagination.

如果你向此事件处理程序发送一个类似 /?page=2&size=20 的有效请求,你将收到如下响应:

¥If you send a valid request like /?page=2&size=20 to this event handler, you will get a response like this:

You are on page 2 with 20 items per page.

如果你发送了一个无效请求且验证失败,H3 将抛出 400 Validation Error 错误。在错误数据中,你将找到可在客户端上使用的验证错误,以便向用户显示友好的错误消息。

¥If you send an invalid request and the validation fails, H3 will throw a 400 Validation Error error. In the data of the error, you will find the validation errors you can use on your client to display a nice error message to your user.

验证主体

¥Validate Body

你可以使用 readValidatedBody 验证正文并获取结果,以替代 readBody

¥You can use readValidatedBody to validate body and get the result, as a replacement of readBody:

import { readValidatedBody } from "h3";
import { z } from "zod";
import * as v from "valibot";

// Example with Zod
const userSchema = z.object({
  name: z.string().min(3).max(20),
  age: z.number({ coerce: true }).positive().int(),
});

// Example with 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 `Hello ${body.name}! You are ${body.age} years old.`;
});

如果你发送一个包含 JSON 格式的有效 POST 请求,如下所示:

¥If you send a valid POST request with a JSON body like this:

{
  "name": "John",
  "age": 42
}

你将收到如下响应:

¥You will get a response like this:

Hello John! You are 42 years old.

如果你发送了一个无效请求且验证失败,H3 将抛出 400 Validation Error 错误。在错误数据中,你将找到可在客户端上使用的验证错误,以便向用户显示友好的错误消息。

¥If you send an invalid request and the validation fails, H3 will throw a 400 Validation Error error. In the data of the error, you will find the validation errors you can use on your client to display a nice error message to your user.

安全解析

¥Safe Parsing

默认情况下,如果每个验证工具(getValidatedRouterParamsgetValidatedQueryreadValidatedBody)的第二个参数直接提供 schema,则验证失败时会抛出 400 Validation Error 错误,但在某些情况下,你可能需要自行处理验证错误。为此,你应该根据所使用的验证库,将实际的安全验证函数作为第二个参数提供。

¥By default if a schema is directly provided as e second argument for each validation utility (getValidatedRouterParams, getValidatedQuery, and readValidatedBody) it will throw a 400 Validation Error error if the validation fails, but in some cases you may want to handle the validation errors yourself. For this you should provide the actual safe validation function as the second argument, depending on the validation library you are using.

回到第一个使用 getValidatedRouterParams 的示例,对于 Zod 来说,它看起来像这样:

¥Going back to the first example with getValidatedRouterParams, for Zod it would look like this:

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) {
    // Handle validation errors
    return `Validation failed:\n${z.prettifyError(params.error)}`;
  }
  return `You are looking for content with topic "${params.data.topic}" and uuid "${params.data.uuid}".`;
});

对于 Valibot 来说,它看起来应该是这样的:

¥And for Valibot, it would look like this:

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) {
    // Handle validation errors
    return `Validation failed:\n${v.summarize(params.issues)}`;
  }
  return `You are looking for content with topic "${params.output.topic}" and uuid "${params.output.uuid}".`;
});