ProvenPin Install

Hono + @hono/zod-openapi typed routes with generated OpenAPI docs: working versions (tested 2026-10-15)

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.

passed2 passed · 0 failed2026-10-15 13:30 UTC
Status
passed
Last passed
2026-10-15 13:30 UTC
Tests
2 / 2
Language
TypeScript
Runtime
node v22.23.3
pinned versions5
hono@4.13.13@hono/zod-openapi@1.6.3zod@4.6.7tsx@4.23.15@types/node@24.19.2
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-zod-openapi") (install), or see them on GitHub.

package.json
{
  "name": "hono-zod-openapi",
  "private": true,
  "type": "module",
  "scripts": {
    "test": "node --import tsx --test test/*.test.ts"
  },
  "dependencies": {
    "hono": "4.13.13",
    "@hono/zod-openapi": "1.6.3",
    "zod": "4.6.7"
  },
  "devDependencies": {
    "tsx": "4.23.15",
    "@types/node": "24.19.2"
  }
}
src/app.ts
import { OpenAPIHono, createRoute, z } from "@hono/zod-openapi";

const User = z.object({ id: z.string().openapi({ example: "u_1" }), name: z.string() }).openapi("User");
const Params = z.object({ id: z.string().min(3).openapi({ param: { name: "id", in: "path" }, example: "u_1" }) });

const getUser = createRoute({
  method: "get",
  path: "/users/{id}",
  request: { params: Params },
  responses: {
    200: { content: { "application/json": { schema: User } }, description: "The user" },
    404: { content: { "application/json": { schema: z.object({ error: z.string() }) } }, description: "Not found" },
  },
});

const db: Record<string, { id: string; name: string }> = { u_1: { id: "u_1", name: "Ada" } };

export const app = new OpenAPIHono();
app.openapi(getUser, (c) => {
  const { id } = c.req.valid("param");
  const u = db[id];
  return u ? c.json(u, 200) : c.json({ error: "not_found" }, 404);
});
app.doc31("/doc", { openapi: "3.1.0", info: { title: "Users API", version: "1.0.0" } });
test/app.test.ts
import { test } from "node:test";
import assert from "node:assert/strict";
import { app } from "../src/app.ts";

test("typed route works and validates params", async () => {
  assert.deepEqual(await (await app.request("/users/u_1")).json(), { id: "u_1", name: "Ada" });
  assert.equal((await app.request("/users/u_9")).status, 404);
  assert.equal((await app.request("/users/x")).status, 400);
});

test("OpenAPI document is generated from the same schemas", async () => {
  const doc = await (await app.request("/doc")).json();
  assert.equal(doc.openapi, "3.1.0");
  assert.ok(doc.paths["/users/{id}"].get.responses["200"]);
  assert.ok(doc.components.schemas.User);
  assert.equal(doc.paths["/users/{id}"].get.parameters[0].name, "id");
});

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.