ProvenPin Install

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 13:30 UTC
Status
passed
Last passed
2026-10-15 13:30 UTC
Tests
3 / 3
Language
TypeScript
Runtime
node v22.23.3
pinned versions5
hono@4.13.13valibot@1.5.0@hono/valibot-validator@0.6.1@types/node@24.19.2vitest@5.0.4
run it
npm install --no-audit --no-fund --ignore-scripts=false
npm test
test environment
node v22.23.3
linux 6.17.0-1022-azure
x64
runner: github-actions 38742087302

The code

Exactly the files that passed. Your agent can pull them with get_recipe("hono-valibot-validator") (install), or see them on GitHub.

package.json
{
  "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"
  }
}
src/app.ts
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 }));
test/app.test.ts
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: "  [email protected] ", password: "longenough" });
    expect(r.status).toBe(201);
    expect(await r.json()).toEqual({ email: "[email protected]", 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.

terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp

Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.