Skip to content

Hono

Hono is a lightweight web framework that runs on Cloudflare Workers, Deno, Bun, Node.js, and more. This guide shows how to serve an OpenAPI spec generated by to-openapi from a Hono application.

Basic Setup

Generate your spec with openapi() and serve it from a GET /openapi.json route.

ts
import { Hono } from "hono";
import { openapi } from "to-openapi";
import { z } from "zod";

const spec = openapi({
  info: {
    title: "Hono API",
    version: "1.0.0",
  },
  servers: [{ url: "http://localhost:3000" }],
  paths: {
    "GET /users": {
      summary: "List all users",
      tags: ["Users"],
      query: z.object({
        limit: z.number().optional(),
        offset: z.number().optional(),
      }),
      200: z.array(
        z.object({
          id: z.string(),
          name: z.string(),
          email: z.string(),
        })
      ),
    },
    "POST /users": {
      summary: "Create a user",
      tags: ["Users"],
      body: z.object({
        name: z.string(),
        email: z.string(),
      }),
      201: z.object({
        id: z.string(),
        name: z.string(),
        email: z.string(),
      }),
      400: { description: "Validation error" },
    },
    "GET /users/{id}": {
      summary: "Get a user by ID",
      tags: ["Users"],
      params: z.object({
        id: z.string(),
      }),
      200: z.object({
        id: z.string(),
        name: z.string(),
        email: z.string(),
      }),
      404: { description: "User not found" },
    },
    "DELETE /users/{id}": {
      summary: "Delete a user",
      tags: ["Users"],
      params: z.object({
        id: z.string(),
      }),
      204: { description: "User deleted" },
      404: { description: "User not found" },
    },
  },
});

const app = new Hono();

// Serve the OpenAPI spec
app.get("/openapi.json", (c) => {
  return c.json(spec);
});

// Implement your actual routes
app.get("/users", (c) => {
  return c.json([]);
});

app.post("/users", async (c) => {
  const body = await c.req.json();
  return c.json({ id: "1", ...body }, 201);
});

app.get("/users/:id", (c) => {
  const id = c.req.param("id");
  return c.json({ id, name: "Alice", email: "alice@example.com" });
});

app.delete("/users/:id", (c) => {
  return c.body(null, 204);
});

export default app;

Using the Class-Based API

The OpenAPI class builder lets you add routes incrementally, which pairs well with Hono's routing style.

ts
import { Hono } from "hono";
import { OpenAPI } from "to-openapi";
import { z } from "zod";

const api = new OpenAPI({
  info: {
    title: "Hono API",
    version: "1.0.0",
  },
  servers: [{ url: "http://localhost:3000" }],
});

const app = new Hono();

// Define a route and register it with the OpenAPI builder
api.route("get", "/posts", {
  summary: "List posts",
  tags: ["Posts"],
  200: z.array(
    z.object({
      id: z.number(),
      title: z.string(),
      body: z.string(),
    })
  ),
});

app.get("/posts", (c) => {
  return c.json([]);
});

api.route("post", "/posts", {
  summary: "Create a post",
  tags: ["Posts"],
  body: z.object({
    title: z.string(),
    body: z.string(),
  }),
  201: z.object({
    id: z.number(),
    title: z.string(),
    body: z.string(),
  }),
});

app.post("/posts", async (c) => {
  const data = await c.req.json();
  return c.json({ id: 1, ...data }, 201);
});

// Serve the generated spec
app.get("/openapi.json", (c) => {
  return c.json(api.document());
});

export default app;

Hono's Built-in OpenAPI Support

Hono has its own OpenAPI integration through @hono/zod-openapi, which tightly couples route definitions with Zod schemas and validation.

Use @hono/zod-openapi when:

  • You want validation and spec generation in one package.
  • Your project exclusively uses Zod.

Use to-openapi when:

  • You use multiple schema libraries (Zod, ArkType, Valibot, or any Standard Schema provider).
  • You need the plugin system for cross-cutting concerns like bearer auth or error responses.
  • You want to merge specs from multiple services using merge().
  • You prefer to keep spec generation separate from your routing logic.