Hono + Valibot request validation (Vitest): working versions (tested 2026-10-15)
Validate JSON bodies and query strings in Hono with Valibot schemas via @hono/valibot-validator, with custom error responses, tested with Vitest.
passed3 passed · 0 failed2026-10-15 11:54 UTC- Status
- passed
- Last passed
- 2026-10-15 11:54 UTC
- Tests
- 3 / 3
- Language
- TypeScript
- Runtime
- node v22.23.3
npm install --no-audit --no-fund --ignore-scripts=false npm test
node v22.23.3 linux 6.17.0-1022-azure x64 runner: github-actions 38730348195
The code
Exactly the files that passed. Your agent can pull them with get_recipe("hono-valibot-validator") (install), or see them on GitHub.
{
"name": "hono-valibot-validator",
"private": true,
"type": "module",
"scripts": {
"test": "vitest run --reporter=default --reporter=json --outputFile=test-report.json"
},
"dependencies": {
"hono": "4.13.13",
"valibot": "1.5.0",
"@hono/valibot-validator": "0.6.1"
},
"devDependencies": {
"@types/node": "24.19.2",
"vitest": "5.0.4"
}
}
import { Hono } from "hono";
import { vValidator } from "@hono/valibot-validator";
import * as v from "valibot";
const Signup = v.object({
email: v.pipe(v.string(), v.trim(), v.toLowerCase(), v.email()),
password: v.pipe(v.string(), v.minLength(8)),
plan: v.optional(v.picklist(["free", "pro"]), "free"),
});
const Paging = v.object({ page: v.optional(v.pipe(v.string(), v.transform(Number), v.integer(), v.minValue(1)), "1") });
export const app = new Hono()
.post("/signup", vValidator("json", Signup, (result, c) => {
if (!result.success) return c.json({ error: "invalid", fields: v.flatten(result.issues).nested }, 422);
}), (c) => c.json(c.req.valid("json"), 201))
.get("/items", vValidator("query", Paging), (c) => c.json({ page: c.req.valid("query").page }));
import { describe, it, expect } from "vitest";
import { app } from "../src/app";
const post = (body: unknown) => app.request("/signup", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body) });
describe("valibot validation", () => {
it("transforms and defaults", async () => {
const r = await post({ email: " ADA@Example.com ", password: "longenough" });
expect(r.status).toBe(201);
expect(await r.json()).toEqual({ email: "ada@example.com", password: "longenough", plan: "free" });
});
it("custom 422 with field errors", async () => {
const r = await post({ email: "bad", password: "short", plan: "gold" });
expect(r.status).toBe(422);
const body = await r.json();
expect(Object.keys(body.fields).sort()).toEqual(["email", "password", "plan"]);
});
it("query transform", async () => {
expect(await (await app.request("/items?page=2")).json()).toEqual({ page: 2 });
expect((await app.request("/items?page=0")).status).toBe(400);
});
});
Connect to ProvenPin
Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp
{ "mcpServers": { "provenpin": { "url": "https://mcp.provenpin.com/mcp" } } }{ "servers": { "provenpin": { "type": "http", "url": "https://mcp.provenpin.com/mcp" } } }{ "mcpServers": { "provenpin": { "serverUrl": "https://mcp.provenpin.com/mcp" } } }{ "mcpServers": { "provenpin": { "type": "streamableHttp", "url": "https://mcp.provenpin.com/mcp" } } }Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.
Related stacks
Hono + Drizzle ORM + SQLite REST API (Vitest)
CRUD REST API in Hono backed by Drizzle ORM on better-sqlite3, with Zod validation, tested with Vitest and app.request().
Astro 7 component rendering tests with the Container API (Vitest)
Render Astro components to HTML in Vitest with the Container API and getViteConfig, checking props, slots and conditional markup.
Hono + @hono/zod-openapi typed routes with generated OpenAPI docs
Define Hono routes with createRoute and Zod schemas so requests are validated, handlers are typed and an OpenAPI 3.1 document is generated at /doc.
Hono + hono/jwt bearer auth with Zod login
Issue HS256 JWTs from a Zod-validated login route in Hono and guard routes with the built-in jwt middleware.
All Hono recipesAll valibot recipesAll @hono/valibot-validator recipesAll vitest recipes