HonoHub Logo

HonoHub

Zod

Using hono-openapi with Zod

Define Validation Schemas

First, you need to define your validation schemas using Zod. Below is an example of how to define schemas for query parameters and response body.

import z from "zod";
// Or 
// import z from "zod/v4"; // If you are using Zod v4

// Validation for query parameters, eg - `/hello?name=John`
const querySchema = z.object({
  name: z.string().optional(),
});

// Validation for response body, eg - `"Hello, John!"`
const responseSchema = z.string();

These schemas will be used to generate the OpenAPI specifications for your Incoming requests and Outgoing responses.

Updating the Router

Code below shows how you might be using Zod with Hono for validation.

import { Hono } from "hono";
import { zValidator } from "@hono/zod-validator";

const app = new Hono();

app.get("/", zValidator("query", querySchema), (c) => {
  const query = c.req.valid("query");
  return c.text(`Hello ${query?.name ?? "Hono"}!`);
});

Now, follow the steps below to integrate Hono OpenAPI -

  1. Inplace of importing the validation middleware (zValidator), from @hono/zod-validator, import it from hono-openapi.
  2. For describing the response body or adding information about the route, import describeRoute from hono-openapi. If you will be defining the response body using Zod, you will also need to import resolver from hono-openapi.
  3. You can uninstall @hono/zod-validator as it is no longer needed.
import { Hono } from "hono";
import {
  validator as zValidator,
  resolver,
  describeRoute,
} from "hono-openapi";

const app = new Hono();

app.get(
  "/",
  describeRoute({
    responses: {
      200: {
        description: "Successful response",
        content: {
          "application/json": {
            schema: resolver(responseSchema),
          },
        },
      },
    },
  }),
  zValidator("query", querySchema),
  (c) => {
    const query = c.req.valid("query");
    return c.text(`Hello ${query?.name ?? "Hono"}!`);
  },
);

And that's it! You have successfully integrated Hono OpenAPI. Now you will have to do this for all the routes you want to document but there is a way to list all the routes, which we will cover in the Generating the OpenAPI Spec section.

You can also pass json schema directly to the responses.200.content["application/json"].schema if you don't want to use resolver from hono-openapi. This is useful if you have a static schema that you want to use for the response body.

Passing Reference

For zod v4, you can use Registry. Here is a demo -

const schema = z
  .object({
    myString: z.string(),
    myUnion: z.union([z.number(), z.boolean()]),
  })
  .describe("My neat object schema").meta({
    ref: "MyNeatObjectSchema",
  });

For zod v3, you can use the zod-openapi library. Here is a demo -

import "zod-openapi/extend";

const schema = z
  .object({
    myString: z.string(),
    myUnion: z.union([z.number(), z.boolean()]),
  })
  .describe("My neat object schema").openapi({
    ref: "MyNeatObjectSchema",
  });

Note

You need to install zod-openapi@4 for this to work properly.

Adding typesense for response body

Simply just create your response schema and pass it in the describeResponse function. This will automatically provide typesense for the response body.

const successResponseSchema = z.object({
  message: z.string(),
});

const errorResponseSchema = z.object({
  error: z.string(),
});

app.get(
  "/",
  describeResponse(
    (c) => {
      return c.json({ message: "Hello Hono!" }, 200);
    },
    {
      200: {
        description: "Success",
        content: {
          "application/json": {
            vSchema: successResponseSchema,
          },
        },
      },
      400: {
        description: "Error",
        content: {
          "application/json": {
            vSchema: errorResponseSchema,
          },
        },
      },
    },
  ),
);

Generating the OpenAPI Spec

For serving the OpenAPI spec, you can use the openAPIRouteHandler from hono-openapi. This will automatically generate the OpenAPI spec based on the routes you have defined in your Hono app.

In the example below, we will be serving the OpenAPI spec at /openapi.json.

import { Hono } from "hono";
import { openAPIRouteHandler } from "hono-openapi";

// Other hono apps
import routes from "./routes";

const main = new Hono();

main.get(
  "/openapi.json",
  openAPIRouteHandler(routes, {
    documentation: {
      info: {
        title: "Hono",
        version: "1.0.0",
        description: "API for greeting users",
      },
    },
  }),
);

Navigate to /openapi.json to see the generated OpenAPI spec. You can also use tools like Scalar or Swagger UI to visualize the OpenAPI spec. Check out the OpenAPI documentation for more details on how to use these tools.

For quick testing, you can also pass includeEmptyPaths: true to the openAPIRouteHandler options. This will include all the routes in the hono app, even if they don't have any OpenAPI documentation. This is useful for debugging and testing purposes.