Skip to content

Typed route builder with schemas (zod) — derive validation, types, OpenAPI, and agent docs from one declaration #3

@parag

Description

@parag

Context

Today, apps built on BoringOS (e.g. the CRM) hand-write an agent API catalog in a context provider — a markdown string describing each HTTP endpoint so agents know what to curl. This string drifts from the actual Hono routes whenever a developer adds or changes an endpoint.

An initial fix (separate workagentDocs as a framework primitive on app.route()) reduces the problem by colocating docs with the mount point and shipping a built-in apiCatalogProvider. But the docs are still hand-written prose that can drift from the actual route handler inside each file.

This issue proposes the structural fix: make the route itself the source of truth.

Proposal

Introduce a typed route builder that wraps Hono and accepts per-route schemas:

app.routeTyped("/api/crm/deals", (r) => {
  r.post({
    summary: "Create a deal",
    body: DealCreateSchema,         // zod
    response: DealSchema,
    agentHint: "Use when the user asks to create a new sales opportunity.",
    handler: async (ctx) => { ... },
  });
  r.get("/:id", { params: z.object({ id: z.string().uuid() }), ... });
});

From one declaration, the framework can derive:

  1. Runtime validation — reject bad bodies/params before the handler runs.
  2. TypeScript types — handler ctx.body is typed from the zod schema.
  3. OpenAPI spec — at /openapi.json. Unlocks SDK generation, Swagger UI, and external integration.
  4. Agent docs, auto-generated — the apiCatalogProvider emits markdown directly from the schema, including param names, body shape, examples derived from z.describe(). No hand-written prose → no drift possible.
  5. MCP tool descriptors (optional future) — expose routes as typed tools instead of curl commands.

Why this matters

  • Drift becomes structurally impossible. Adding a new field to a request body updates validation, types, docs, and the agent prompt in one edit.
  • Cross-cutting wins. Frontend SDK, external integrators, and agents all read from the same schema.
  • Better agent behavior. Agents get typed shapes instead of prose — fewer malformed requests, fewer retries.

Scope / cost

Non-trivial. Requires:

  • Designing the routeTyped builder API (borrowing ideas from @hono/zod-openapi, trpc, hattip).
  • Zod as a new framework-level dependency (or an adapter so callers can bring their own validator).
  • Migrating the existing framework routes (/api/auth, /api/admin, /api/copilot, /api/agent) to the typed builder — and coordinating app-side migrations (the CRM alone has ~40 routes).

Realistic path: ship the builder alongside the existing app.route() so migration is gradual. New routes use the typed builder; old routes keep working until migrated.

Prior art

  • @hono/zod-openapi — zod-powered route definitions with auto OpenAPI on Hono. Most direct inspiration.
  • tRPC — schema-first server, but RPC rather than REST.
  • fastify with @fastify/type-provider-typebox — similar pattern in fastify ecosystem.
  • elysia.js — type-driven from the ground up.

Open questions

  • Zod v3 vs v4 vs Valibot vs StandardSchema? Lean toward StandardSchema to stay validator-agnostic.
  • Does the OpenAPI spec need to be served from every app, or is one combined spec (mounted by the framework) enough?
  • How do we express auth requirements (admin-only, tenant-scoped, agent-only) declaratively so they show up in both validation and docs?

Filed as a follow-up to the agentDocs primitive work. That unblocks near-term drift prevention; this is the long-term structural answer.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions