Validate Data
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
is not a validation. You must validate the data before using it.Utilities for Validation
H3 provide some utilities to help you to handle data validation. You will be able to validate:
- query with
getValidatedQuery
- params with
getValidatedRouterParams
. - body with
readValidatedBody
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).
Let's see how to validate data with Zod and Valibot.
Validate Params
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}".`;
},
);
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".
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
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.`;
});
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.
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.
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
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.`;
});
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.
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
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.
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}".`;
});
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}".`;
});