# How To Generate an OpenAPI Document With Hono

# How to generate an OpenAPI document with Hono

This guide demonstrates generating an OpenAPI document for a [Hono](https://hono.dev/) API and using Speakeasy to create an SDK based on the generated document.

This guide covers:

1. Adding Hono OpenAPI and Scalar UI to a Node.js Hono project.
2. Generating an OpenAPI document using the Hono OpenAPI middleware with Zod validation.
3. Improving the generated OpenAPI document to prepare it for code generation.
4. Using the Speakeasy CLI to generate an SDK based on the OpenAPI document.
5. Adding Speakeasy OpenAPI extensions to improve the generated SDK.

The generated SDK usage will also be demonstrated.

While the example application is simplified, the steps outlined should translate well to any Hono project.

## The OpenAPI creation pipeline

[Hono OpenAPI](https://hono.dev/examples/hono-openapi) is middleware that enables automatic OpenAPI documentation generation for Hono APIs by integrating with validation libraries like Zod, Valibot, ArkType, and TypeBox. This guide uses [Zod](https://zod.dev/) to define data schemas and validate values, then sets up the Hono app to generate an OpenAPI document.

The quality of the OpenAPI document determines the quality of generated SDKs and documentation, so techniques for improving the generated document will be explored based on the Speakeasy [OpenAPI best practices](/docs/prep-openapi/best-practices).

The improved OpenAPI document will then be used to generate an SDK using Speakeasy.

Instructions for adding SDK creation to a CI/CD pipeline will be provided so that Speakeasy automatically generates fresh SDKs whenever the Hono API changes in the future.

Finally, a simplified example will demonstrate how to use the generated SDK.

## Requirements

Ensure the following are installed:

- [Node.js 18.14.1 or above](https://nodejs.org/en/download) (Node v22.15.1 was used for testing).
- The [Speakeasy CLI](/docs/speakeasy-cli/getting-started), which will be used to generate an SDK from the OpenAPI document.

<Callout title="Sample code" type="info">
  The source code for the examples throughout this guide are available in the [Speakeasy Hono
  example repository](https://github.com/speakeasy-api/examples/tree/main/frameworks-hono-ts).
</Callout>

This guide starts from scratch with a new Hono project, but anyone adding OpenAPI documentation to an existing Hono project can jump straight to step 3.

## Step 1: Scaffold a Hono project

Those starting from scratch, run the Hono project scaffold command to get a basic setup.

```bash filename="Terminal"
npm create hono@latest users-api
```

When prompted, select `nodejs` as the template. Then install dependencies:

```bash filename="Terminal"
cd users-api && npm install
```

## Step 2: Create a basic Web API

The scaffold generates a single `src/index.ts`. Split this into two files:

- `src/index.ts` — starts the HTTP server
- `src/app.ts` — defines the Hono app and routes

Replace `src/index.ts` with:

```typescript filename="src/index.ts"

const port = 3000
console.log(`Server running on http://localhost:${port}`)

serve({ fetch: app.fetch, port })
```

Create `src/app.ts`:

```typescript filename="src/app.ts"

const app = new Hono();

app.get('/users', (c) => {
  return c.json([
    { id: '1', name: 'John Doe', age: 42 },
    { id: '2', name: 'Sarah Jones', age: 32 },
  ])
});

app.post('/users', async (c) => {
  const body = await c.req.json()
  return c.json({ id: '3', ...body }, 200)
});

app.get('/users/:id', (c) => {
  const id = c.req.param('id')
  return c.json({ id, name: 'John Doe', age: 42 })
});

```

Run `npm run dev` and open [`http://localhost:3000/users`](http://localhost:3000/users) to confirm the API is working.

## Step 3: Add Hono OpenAPI middleware

The [Hono OpenAPI](https://hono.dev/examples/hono-openapi) middleware will be used to generate an OpenAPI document. This middleware works with validation libraries through [Standard Schema](https://standardschema.dev/), including Zod, Valibot, ArkType, and TypeBox. 

This guide will use Zod (v4) schemas to validate values and types and to generate part of the OpenAPI document, but the same principles apply to the other supported validation libraries.

First, install the middleware and Zod:

```bash filename="Terminal"
npm i hono-openapi @hono/standard-validator zod@v4
```

Next, create a `schemas.ts` file in the `src` folder, with Zod schemas for the data:

```typescript filename="schema.ts"

```

The `z` object is imported directly from `zod` unlike other Hono/Zod integrations that modify it and extent it a bit. This is just a plain Zod instance, so check out the Zod documentation for more guidance.

One important thing here is `.meta({ ref: "..." })`, which registers schemas as [reusable components](/openapi/components) exactly based on OpenAPI's component structure and the JSON Schema `$ref` keyword.

Schemas can be created for anything that would benefit from a data schema, validation properties, and human-readable descriptions. This includes, but is not limited to, the JSON request body, query parameters, headers, and response data, and error messages.

```typescript filename="schema.ts"

```

## Step 4: Describe what endpoints should be doing with describeRoutes

With `hono-openapi`, the OpenAPI metadata and schemas are added through the `describeRoute` middleware.

For better code organization, and to avoid overloading `src/app.ts` with OpenAPI descriptions, consider splitting the route description objects from the route handlers. 

Create a `users.routes.ts` file in the `src/routes/users` folder, with route description objects like this following:

```typescript filename="users.routes.ts"

```

These route description objects define the OpenAPI specification for each route. The `resolver` function converts Zod schemas to OpenAPI schemas, which will be used for request validation automatically when the `validator` middleware is used in the route handlers.

## Step 5: Implement route handlers

With the endpoints described by OpenAPI in `users.routes.ts` it's time to define the route handler logic itself. This is what the endpoint will actually do. Create something. Save something. Send something. 

To implement this code we need another file, so create a `users.handlers.ts` file in the `src/routes/users` folder and use the following placeholder code.

```typescript filename="users.handlers.ts"

};

  console.log({ user });
  // TODO: db query to create a user
  return c.json(
    {
      id: "2342",
      age: user.age,
      name: user.name,
    },
    200,
  );
};

  // TODO: db query to get a user by id
  const foundUser = {
    age: 50,
    id,
    name: "Lisa Smith",
  };

  if (!foundUser) {
    return c.json(
      {
        message: "Not found",
      },
      404,
    );
  }
  return c.json(foundUser, 200);
};
```

With `hono-openapi`, handlers accept the one argument, which is Hono's standard `Context` type. This gives access to the request data and other validation middleware logic, which can be accessed using `c.req.valid()`. 

The schemas are there, the app logic is written, but now it all needs to be brought together.

## Step 6: Register application routes with the Hono router

For each endpoint in the API, it needs a route, a HTTP method, a path, and a handler. That's regular Hono usage, but what's extra for OpenAPI here is the two middlewares being added in: `describeRoute` and `zValidator`. 

```typescript filename="users.index.ts"

const router = new Hono();

router.get("/users", describeRoute(routes.listRoute), handlers.list);

router.post(
  "/users",
  describeRoute(routes.createRoute),
  zValidator("json", UserInsertSchema),
  handlers.create
);

router.get(
  "/users/:id",
  describeRoute(routes.getOneRoute),
  zValidator("param", idParamsSchema),
  handlers.getOne
);

```

The important bits here are these two validators:

- `describeRoute` middleware to add OpenAPI documentation to the route based on the route description objects defined in `users.routes.ts`.
- `validator` (aliased as `zValidator`) middleware to validate requests and automatically add request schemas to OpenAPI.

By using the same Zod schema instance to both power the actual server-side validation logic, and generate the OpenAPI description (which is then used for documentation, mock servers, SDK generation, etc.) the implementation cannot get out of sync with the OpenAPI, something people have struggled with for years.

## Step 7: Exporting OpenAPI and API reference documentation

Once all this is set up, it's time to go about exporting the OpenAPI, and therefore creating API Reference Documentation, SDKs, or any other artifacts from this OpenAPI. One popular approach is to offer this up over HTTP so it can be used by anything with access to the API domain, therefore ensuring the correct version of the OpenAPI is being looked at.

One way to do this is with a `src/lib/configureOpenAPI.ts` file, that does three things:

1. Add all the higher-level OpenAPI that's not covered on the route-specific Zod-based stuff so far.
2. Add a route for `/openapi` to export the OpenAPI itself.
3. Add a route for `/docs` which uses Scalar UI to turn that OpenAPI into beautiful simple API documentation.

Option 3 is optional, and will need the following NPM package to make it work: 

```bash  filename="Terminal"
npm install @scalar/hono-api-reference
```

The code to do all of this will look like this.

```typescript filename="src/lib/configureOpenAPI.ts"

  app.get(
    "/docs",
    Scalar({
      url: "/openapi",
      pageTitle: "Users Management API",
    })
  );
}
```

Now, pass in the Hono app instance to the `configureOpenAPI` function in the `src/app.ts` file:

```typescript filename="app.ts"

const app = new Hono();

configureOpenAPI(app);
```

To confirm this has all worked, go to [`http://localhost:3000/openapi`](http://localhost:3000/openapi) to see the OpenAPI description in JSON, and navigate to [`http://localhost:3000/docs`](http://localhost:3000/docs) to see Scalar UI showing off the API documentation nicely.

![Scalar UI endpoints](/assets/openapi/hono/scalar-ui.png)

## Step 8: Save the OpenAPI description to a file

To generate an SDK, save the OpenAPI description as a YAML file. First, install the js-yaml package:

```bash filename="Terminal"
npm i js-yaml && npm i --save-dev @types/js-yaml
```

Create a script called `generateOpenApiDocument.ts` in the `src/` folder. This script imports the whole Hono application, and the `openAPIDocumentation` from `src/lib/configureOpenAPI.ts` which brings in the generic OpenAPI not defined elsewhere in the application. Then it uses `generateSpecs` to extract the OpenAPI document, and writes it as YAML.

```typescript filename="src/generateOpenApiDocument.ts"

async function main() {
  const specs = await generateSpecs(mainApp, {
    documentation: openAPIDocumentation,
  });

  fs.writeFileSync("openapi.yaml", yaml.dump(specs));
}

main();
```

Add a script to `package.json`:

```json filename="package.json"
"generate:openapi": "npx tsx ./src/generateOpenApiDocument.ts"
```

Run the script:

```bash filename="Terminal"
npm run generate:openapi
```

An `openapi.yaml` file will be created in the root folder.

## Step 9: Lint the OpenAPI document

Even though this OpenAPI is being generated from Zod and Hono, it's still possible to make some mistakes. To guide you through this process the Speakeasy CLI has an [OpenAPI linting](/docs/prep-openapi/linting#configuration) built in, which can check the OpenAPI document for errors and style issues.

Run the linting command:

```bash filename="Terminal"
speakeasy lint openapi --schema ./openapi.yaml
```

A lint report will be displayed in the terminal, showing errors, warnings, and hints:

![Speakeasy lint report](/assets/openapi/hono/speakeasy-lint-report.png)

The Speakeasy Linter has a set of [rules](/docs/prep-openapi/linting#available-rules) that can be configured.

Speakeasy has a VS Code extension to help validate OpenAPI documents for the creation of production-grade SDKs.

For advanced customization, Speakeasy [transformations](/docs/prep-openapi/transformations) can modify the OpenAPI document (remove unused components, filter operations), and [overlays](/docs/prep-openapi/overlays/create-overlays) can add extensions or examples without modifying the original document.

## Step 10: Improving OpenAPI quality 

Now that the OpenAPI is being generated as a local file and/or a URL and linted for validity, it is time to really polish this OpenAPI to make more tools have more information and therefore be more useful.

### Add operationIds to routes

In the OpenAPI document, each HTTP request has an `operationId` that identifies the operation. The `operationId` is used to generate method names and documentation in SDKs, and often used for clean URLs in documentation, so it's good to have them.

Add an `operationId` to each route documentation object:

```typescript filename="users.routes.ts"

```

### Add tags to routes

Tags group operations in generated SDK code and documentation, improving navigation and URL structure. Add them to all routes using the `tags` property:

```typescript filename="users.routes.ts"

```

Add tag metadata by including a [Tag Object](https://spec.openapis.org/oas/v3.1.2.html#tag-object) in the documentation configuration:

```typescript filename="configureOpenAPI.ts"

```

### Add servers to the OpenAPI document

Speakeasy expects a list of servers at the root of the OpenAPI document. Add a `servers` property to the documentation object:

```typescript filename="configureOpenAPI.ts"

```

## Step 11: Add retries with x-speakeasy-retries

[Speakeasy extensions](/docs/speakeasy-reference/extensions) enable vendor-specific functionality in the OpenAPI document. Extension fields are prefixed with `x-speakeasy-`.

Add the `x-speakeasy-retries` extension to configure automatic retry behavior for SDK requests. Apply it globally in the documentation object:

```typescript filename="configureOpenAPI.ts"

```

Override the global retry strategy for a specific route by adding `x-speakeasy-retries` to that route's documentation object:

```typescript filename="users.routes.ts"

```

## Step 12: Create the SDK

The [`quickstart`](/docs/speakeasy-cli/quickstart) command will be used for a guided SDK setup.

Run the command using the Speakeasy CLI:

```bash filename="Terminal"
speakeasy quickstart
```

Following the prompts, provide the OpenAPI document location, name the SDK `SDK`, and select `TypeScript` as the SDK language.

In the terminal, the steps taken by Speakeasy to create the SDK will be displayed.

```
│ Workflow - success
│ └─Target: sdk - success
│   └─Source: Users API - success
│     └─Validating Document - success
│     └─Diagnosing OpenAPI - success
│     └─Tracking OpenAPI Changes - success
│       └─Snapshotting OpenAPI Revision - success
│       └─Storing OpenAPI Revision - success
│   └─Validating gen.yaml - success
│   └─Generating Typescript SDK - success
│     └─Setup Environment - success
│     └─Load and Validate Document - success
│     └─Generate SDK - success
│     └─Compile SDK - success
│     └─Setup Environment - success
│     └─Load and Validate Document - success
│     └─Generate SDK - success
│   └─Generating Code Samples - success
│     └─Snapshotting Code Samples - success
│       └─Snapshotting Code Samples - success
│       └─Uploading Code Samples - success
```

Speakeasy [validates](/docs/sdks/core-concepts#validation) the OpenAPI document to check that it is ready for code generation. Validation issues will be printed in the terminal. The generated SDK will be saved as a folder in the project.

If ESLint styling errors occur, running the `speakeasy quickstart` command from outside the project will resolve them.

Speakeasy also suggests improvements for the SDK using [Speakeasy Suggest](/docs/prep-openapi/maintenance), which is an AI-powered tool in Speakeasy Studio. Suggestions can be viewed by opening the link to the Speakeasy Studio workspace in the terminal:

![Speakeasy Studio showing SDK improvement suggestions](/assets/openapi/hono/speakeasy-studio-suggestions.png)

## Adding SDK generation to GitHub Actions

The Speakeasy [`sdk-generation-action`](https://github.com/speakeasy-api/sdk-generation-action) repository provides workflows for integrating the Speakeasy CLI into CI/CD pipelines to automatically regenerate SDKs when the OpenAPI document changes.

Speakeasy can be set up to automatically push a new branch to SDK repositories so that engineers can review and merge the SDK changes.

For an overview of how to set up automation for SDKs, see the Speakeasy [SDK Workflow Matrix](/docs/workflow-reference/generation-reference).

## Using the SDK

Once an SDK has been generated, it can be [published](/docs/publish-sdk) for use. For TypeScript, it can be published as an npm package.

A quick, non-production-ready way to see the SDK in action is to copy the SDK folder to a frontend TypeScript project and use it there.

For example, a Vite project that uses TypeScript can be created:

```bash filename="Terminal"
npm create vite@latest
```

Copy the SDK folder from the Hono app to the `src` directory of the TypeScript Vite project and delete the SDK folder in the Hono project.

In the SDK `README.md` file, documentation about the Speakeasy SDK can be found. TypeScript SDKs generated with Speakeasy include an installable [Model Context Protocol (MCP) server](https://www.speakeasy.com/docs/standalone-mcp/build-server) where the various SDK methods are exposed as tools that AI applications can invoke.
The SDK documentation includes instructions for installing the MCP server.

Note that the SDK is not ready for production use. To get it production-ready, follow the steps outlined in the Speakeasy workspace.

The SDK includes Zod as a bundled dependency, as can be seen in the `sdk-typescript/package.json` file.

Replace the code in the `src/main.ts` file with the following example code taken from the `sdk-typescript/docs/sdks/users/README.md` file:

```typescript filename="main.ts"

async function run() {
  const result = await sdk.users.getUsers();

  // Handle the result
  console.log({ result });
}

run();
```

Run the Vite dev server:

```bash filename="Terminal"
npm run dev
```

Enable CORS in the Hono dev server by importing the built-in CORS middleware in the `src/app.ts` file:

```typescript filename="app.ts"

```

Add the middleware and set the `origin` to the Vite dev server URL:

```typescript filename="app.ts"
app.use(
  "/users",
  cors({
    origin: "http://localhost:5173",
  }),
);
```

Run the Hono dev server as well:

```bash filename="Terminal"
npm run dev
```

The following will be logged in the browser dev tools console:

```json
{
  "result": [
    {
      "id": "123",
      "name": "John Doe",
      "age": 42
    },
    {
      "id": "124",
      "name": "Sarah Jones",
      "age": 32
    }
  ]
}
```

The SDK functions are type safe and include TypeScript autocompletion for arguments and outputs.

If attempting to access a property that doesn't exist:

```typescript filename="main.ts"
const userOne = result[0].surname;
```

A TypeScript error will be displayed:

```
Property 'surname' does not exist on type 'UserSelect'
```

## Further reading

This guide covered the basics of generating an OpenAPI document using Hono. Here are some resources for learning more about OpenAPI, the Hono OpenAPI middleware, and Speakeasy:

- [The Hono OpenAPI documentation](https://honohub.dev/docs/openapi): Learn more about generating an OpenAPI document and validating values and types using multiple validation libraries through Standard Schema. The topics covered include installation, configuration, validation, and advanced usage.
- [The Hono OpenAPI examples with Zod](https://honohub.dev/docs/openapi/zod): Official guide for using hono-openapi with Zod.
- [The Hono examples on OpenAPI](https://hono.dev/examples/hono-openapi): Official Hono documentation with examples of using the hono-openapi middleware.

- [The Speakeasy documentation](/docs): Speakeasy has extensive documentation covering how to generate SDKs from OpenAPI documents, how to customize SDKs, and more.
- [The Speakeasy OpenAPI reference](/openapi): View a detailed reference for the OpenAPI Specification.

- [The Speakeasy Blog](/blog): Read articles about OpenAPI, SDK generation, and more, including:
  - [Native JSONL support in your SDKs](/blog/release-jsonl-support)
  - [Introducing comprehensive SDK testing](/blog/release-sdk-testing)
  - [Model Context Protocol: TypeScript SDKs for the agentic AI ecosystem](/blog/release-model-context-protocol)
