Hono RPC typed client with zod validator: working versions (tested 2026-10-15)
Expose a Hono app as an RPC-typed AppType, call it in-process with hc and app.request, and validate JSON bodies with @hono/zod-validator.
passed2 passed · 0 failed2026-10-15 14:59 UTC- Status
- passed
- Last passed
- 2026-10-15 14:59 UTC
- Tests
- 2 / 2
- 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 38755782594
The code
Exactly the files that passed. Your agent can pull them with get_recipe("hono-rpc-typed-client") (install), or see them on GitHub.
{
"name": "hono-rpc-typed-client",
"private": true,
"type": "module",
"scripts": {
"test": "node --import tsx --test test/*.test.ts"
},
"dependencies": {
"hono": "4.13.13",
"zod": "4.6.7",
"@hono/zod-validator": "0.9.1"
},
"devDependencies": {
"@types/node": "26.6.5",
"tsx": "4.23.15"
}
}
import { Hono } from "hono";
import { zValidator } from "@hono/zod-validator";
import { z } from "zod";
const CreateItem = z.object({
name: z.string().min(2),
qty: z.number().int().positive(),
});
const items: Array<{ id: number; name: string; qty: number }> = [];
export const app = new Hono()
.post("/items", zValidator("json", CreateItem), (c) => {
const body = c.req.valid("json");
const item = { id: items.length + 1, ...body };
items.push(item);
return c.json(item, 201);
})
.get("/items/:id", (c) => {
const id = Number(c.req.param("id"));
const item = items.find((i) => i.id === id);
if (!item) return c.json({ error: "not_found" }, 404);
return c.json(item);
})
.get("/items", (c) => c.json(items));
export type AppType = typeof app;
export function reset() {
items.length = 0;
}
import { hc } from "hono/client";
import type { AppType } from "./app.ts";
/** Build an RPC client against any fetch-compatible base (including app.request). */
export function makeClient(baseUrl: string, fetchImpl: typeof fetch) {
return hc<AppType>(baseUrl, { fetch: fetchImpl });
}
import { test } from "node:test";
import assert from "node:assert/strict";
import { app, reset } from "../src/app.ts";
import { makeClient } from "../src/client.ts";
test("rpc client creates and fetches an item", async () => {
reset();
const client = makeClient("http://local", (input, init) => app.request(input, init));
const created = await client.items.$post({ json: { name: "bolt", qty: 3 } });
assert.equal(created.status, 201);
const body = await created.json();
assert.equal(body.name, "bolt");
const got = await client.items[":id"].$get({ param: { id: String(body.id) } });
assert.equal(got.status, 200);
assert.deepEqual(await got.json(), body);
});
test("invalid body is 400", async () => {
reset();
const res = await app.request("/items", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ name: "x", qty: 0 }),
});
assert.equal(res.status, 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().
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.
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 + Zod validator API
Hono routes with @hono/zod-validator for JSON and query validation, tested with app.request().
All Hono recipesAll Zod recipesAll @hono/zod-validator recipes