2023-08-19 01:43:36 +08:00
# Zod OpenAPI Hono
2025-03-19 16:53:11 +08:00
[](https://codecov.io/github/honojs/middleware)
2023-08-19 15:07:59 +08:00
**Zod OpenAPI Hono** is an extended Hono class that supports OpenAPI. With it, you can validate values and types using [**Zod** ](https://zod.dev/ ) and generate OpenAPI Swagger documentation. This is based on [**Zod to OpenAPI** ](https://github.com/asteasolutions/zod-to-openapi ) (thanks a lot!). For details on creating schemas and defining routes, please refer to [the "Zod to OpenAPI" resource ](https://github.com/asteasolutions/zod-to-openapi ).
2023-08-19 01:43:36 +08:00
2023-08-19 15:07:59 +08:00
_Note: This is not standalone middleware but is hosted on the monorepo "[github.com/honojs/middleware](https://github.com/honojs/middleware)"._
2023-08-19 01:43:36 +08:00
## Usage
2023-08-19 09:31:58 +08:00
### Installation
2023-08-19 01:43:36 +08:00
2023-08-19 15:07:59 +08:00
You can install it via npm. It should be installed alongside `hono` and `zod` .
2023-08-19 09:31:58 +08:00
```sh
2023-08-19 01:43:36 +08:00
npm i hono zod @hono/zod -openapi
```
2023-08-19 09:31:58 +08:00
### Basic Usage
2023-08-19 01:43:36 +08:00
2023-08-19 15:07:59 +08:00
#### Setting up your application
2023-08-19 09:31:58 +08:00
2023-08-19 15:07:59 +08:00
First, define your schemas with Zod. The `z` object should be imported from `@hono/zod-openapi` :
2023-08-19 01:43:36 +08:00
```ts
import { z } from '@hono/zod-openapi'
const ParamsSchema = z.object({
id: z
.string()
.min(3)
.openapi({
param: {
name: 'id',
in: 'path',
},
example: '1212121',
}),
})
const UserSchema = z
.object({
2023-08-19 09:31:58 +08:00
id: z.string().openapi({
2023-08-19 22:55:27 +08:00
example: '123',
2023-08-19 01:43:36 +08:00
}),
name: z.string().openapi({
example: 'John Doe',
}),
age: z.number().openapi({
example: 42,
}),
})
.openapi('User')
```
2024-10-11 15:34:25 +08:00
> [!TIP]
2024-12-19 09:26:01 +08:00
>
2024-10-11 15:34:25 +08:00
> `UserSchema` schema will be registered as `"#/components/schemas/User"` refs in the OpenAPI document.
2024-02-05 05:36:50 +08:00
> If you want to register the schema as referenced components, use `.openapi()` method.
2023-12-07 20:31:03 +08:00
2023-08-19 15:07:59 +08:00
Next, create a route:
2023-08-19 01:43:36 +08:00
```ts
import { createRoute } from '@hono/zod-openapi'
const route = createRoute({
method: 'get',
2023-08-23 10:49:32 +08:00
path: '/users/{id}',
2023-08-19 01:43:36 +08:00
request: {
params: ParamsSchema,
},
responses: {
200: {
content: {
'application/json': {
schema: UserSchema,
},
},
2023-08-19 15:07:59 +08:00
description: 'Retrieve the user',
2023-08-19 01:43:36 +08:00
},
2023-08-19 09:31:58 +08:00
},
})
```
2023-08-19 15:07:59 +08:00
Finally, set up the app:
2023-08-19 09:31:58 +08:00
```ts
import { OpenAPIHono } from '@hono/zod-openapi'
const app = new OpenAPIHono()
app.openapi(route, (c) => {
const { id } = c.req.valid('param')
2024-05-15 09:06:11 +08:00
return c.json(
{
id,
age: 20,
name: 'Ultra-man',
},
200 // You should specify the status code even if it is 200.
)
2023-08-19 09:31:58 +08:00
})
2023-08-19 15:07:59 +08:00
// The OpenAPI documentation will be available at /doc
2023-08-19 09:31:58 +08:00
app.doc('/doc', {
openapi: '3.0.0',
info: {
version: '1.0.0',
title: 'My API',
},
})
```
2023-08-19 15:07:59 +08:00
You can start your app just like you would with Hono. For Cloudflare Workers and Bun, use this entry point:
```ts
export default app
```
2024-12-19 09:26:01 +08:00
> [!IMPORTANT]
> The request must have the proper `Content-Type` to ensure the validation. For example, if you want to validate a JSON body, the request must have the `Content-Type` to `application/json` in the request. Otherwise, the value of `c.req.valid('json')` will be `{}`.
>
> ```ts
> import { createRoute, z, OpenAPIHono } from '@hono/zod-openapi'
>
> const route = createRoute({
> method: 'post',
> path: '/books',
> request: {
> body: {
> content: {
> 'application/json': {
> schema: z.object({
> title: z.string(),
> }),
> },
> },
> },
> },
> responses: {
> 200: {
> description: 'Success message',
> },
> },
> })
>
> const app = new OpenAPIHono()
>
> app.openapi(route, (c) => {
> const validatedBody = c.req.valid('json')
> return c.json(validatedBody) // validatedBody is {}
> })
>
> const res = await app.request('/books', {
> method: 'POST',
> body: JSON.stringify({ title: 'foo' }),
> // The Content-Type header is lacking.
> })
>
> const data = await res.json()
> console.log(data) // {}
> ```
>
> If you want to force validation of requests that do not have the proper `Content-Type`, set the value of `request.body.required` to `true`.
>
> ```ts
> const route = createRoute({
> method: 'post',
> path: '/books',
> request: {
> body: {
> content: {
> 'application/json': {
> schema: z.object({
> title: z.string(),
> }),
> },
> },
> required: true, // <== add
> },
> },
> })
> ```
2023-08-19 15:07:59 +08:00
### Handling Validation Errors
2023-08-19 09:31:58 +08:00
2023-08-19 15:07:59 +08:00
Validation errors can be handled as follows:
2023-08-19 09:31:58 +08:00
2023-08-19 15:07:59 +08:00
First, define the error schema:
2023-08-19 09:31:58 +08:00
```ts
const ErrorSchema = z.object({
code: z.number().openapi({
example: 400,
}),
message: z.string().openapi({
example: 'Bad Request',
}),
})
```
2023-08-19 15:07:59 +08:00
Then, add the error response:
2023-08-19 09:31:58 +08:00
```ts
const route = createRoute({
method: 'get',
2023-08-23 10:49:32 +08:00
path: '/users/{id}',
2023-08-19 09:31:58 +08:00
request: {
params: ParamsSchema,
},
responses: {
2023-08-19 01:43:36 +08:00
400: {
content: {
'application/json': {
schema: ErrorSchema,
},
},
2023-08-19 15:07:59 +08:00
description: 'Returns an error',
2023-08-19 01:43:36 +08:00
},
},
})
```
2023-08-19 15:07:59 +08:00
Finally, add the hook:
2023-08-19 01:43:36 +08:00
```ts
app.openapi(
route,
(c) => {
const { id } = c.req.valid('param')
2024-05-15 09:06:11 +08:00
return c.json(
{
id,
age: 20,
name: 'Ultra-man',
},
200
)
2023-08-19 01:43:36 +08:00
},
2023-08-19 09:31:58 +08:00
// Hook
2023-08-19 01:43:36 +08:00
(result, c) => {
if (!result.success) {
2023-12-29 03:25:02 +08:00
return c.json(
2023-08-19 01:43:36 +08:00
{
2023-08-19 09:31:58 +08:00
code: 400,
2023-08-19 15:07:59 +08:00
message: 'Validation Error',
2023-08-19 01:43:36 +08:00
},
400
)
}
}
)
2023-08-19 09:31:58 +08:00
```
2023-08-19 01:43:36 +08:00
2023-09-26 16:09:02 +08:00
### A DRY approach to handling validation errors
In the case that you have a common error formatter, you can initialize the `OpenAPIHono` instance with a `defaultHook` .
```ts
const app = new OpenAPIHono({
defaultHook: (result, c) => {
if (!result.success) {
2023-12-29 03:25:02 +08:00
return c.json(
2023-09-26 16:09:02 +08:00
{
ok: false,
errors: formatZodErrors(result),
source: 'custom_error_handler',
},
422
)
}
},
})
```
You can still override the `defaultHook` by providing the hook at the call site when appropriate.
```ts
// uses the defaultHook
app.openapi(createPostRoute, (c) => {
const { title } = c.req.valid('json')
2023-12-29 03:25:02 +08:00
return c.json({ title })
2023-09-26 16:09:02 +08:00
})
// override the defaultHook by passing in a hook
app.openapi(
createBookRoute,
(c) => {
const { title } = c.req.valid('json')
2024-05-15 09:06:11 +08:00
return c.json({ title }, 200)
2023-09-26 16:09:02 +08:00
},
(result, c) => {
if (!result.success) {
2023-12-29 03:25:02 +08:00
return c.json(
2023-09-26 16:09:02 +08:00
{
ok: false,
source: 'routeHook' as const,
},
400
)
}
}
)
```
2023-09-12 07:25:41 +08:00
### OpenAPI v3.1
You can generate OpenAPI v3.1 spec using the following methods:
```ts
2024-08-29 14:12:57 +08:00
app.doc31('/docs', { openapi: '3.1.0', info: { title: 'foo', version: '1' } }) // new endpoint
2024-12-19 09:26:01 +08:00
app.getOpenAPI31Document({
openapi: '3.1.0',
info: { title: 'foo', version: '1' },
}) // schema object
2023-09-12 07:25:41 +08:00
```
2023-09-05 17:55:09 +08:00
### The Registry
You can access the [`OpenAPIRegistry` ](https://github.com/asteasolutions/zod-to-openapi#the-registry ) object via `app.openAPIRegistry` :
```ts
const registry = app.openAPIRegistry
```
2023-08-19 09:31:58 +08:00
### Middleware
2023-08-19 15:07:59 +08:00
Zod OpenAPI Hono is an extension of Hono, so you can use Hono's middleware in the same way:
2023-08-19 09:31:58 +08:00
```ts
import { prettyJSON } from 'hono/pretty-json'
//...
app.use('/doc/*', prettyJSON())
2023-08-19 01:43:36 +08:00
```
2023-09-19 07:24:10 +08:00
### Configure middleware for each endpoint
You can configure middleware for each endpoint from a route created by `createRoute` as follows.
```ts
import { prettyJSON } from 'hono/pretty-json'
2024-08-09 10:36:33 +08:00
import { cache } from 'hono/cache'
2023-09-19 07:24:10 +08:00
2023-09-26 16:09:02 +08:00
app.use(route.getRoutingPath(), prettyJSON(), cache({ cacheName: 'my-cache' }))
2023-09-19 07:24:10 +08:00
app.openapi(route, handler)
```
2024-04-11 18:16:12 +08:00
Or you can use the `middleware` property in the route definition.
```ts
const route = createRoute({
method: 'get',
path: '/users/{id}',
request: {
params: ParamsSchema,
},
2024-12-19 09:26:01 +08:00
middleware: [prettyJSON(), cache({ cacheName: 'my-cache' })] as const, // Use `as const` to ensure TypeScript infers the middleware's Context.
2024-04-11 18:16:12 +08:00
responses: {
200: {
content: {
'application/json': {
schema: UserSchema,
},
},
description: 'Retrieve the user',
},
},
})
```
2023-08-19 15:07:59 +08:00
### RPC Mode
2023-08-19 09:40:55 +08:00
2023-08-19 15:07:59 +08:00
Zod OpenAPI Hono supports Hono's RPC mode. You can define types for the Hono Client as follows:
2023-08-19 09:40:55 +08:00
```ts
import { hc } from 'hono/client'
const appRoutes = app.openapi(route, (c) => {
const data = c.req.valid('json')
2024-05-15 09:06:11 +08:00
return c.json(
{
id: data.id,
message: 'Success',
},
200
)
2023-08-19 09:40:55 +08:00
})
const client = hc< typeof appRoutes > ('http://localhost:8787/')
```
2023-11-21 15:58:48 +08:00
## Tips
### How to register components
You can register components to the registry as follows:
```ts
app.openAPIRegistry.registerComponent('schemas', {
User: UserSchema,
})
```
About this feature, please refer to [the "Zod to OpenAPI" resource / Defining Custom Components ](https://github.com/asteasolutions/zod-to-openapi#defining-custom-components )
### How to setup authorization
You can setup authorization as follows:
eg. Bearer Auth
Register the security scheme:
```ts
2024-05-15 09:06:11 +08:00
app.openAPIRegistry.registerComponent('securitySchemes', 'Bearer', {
type: 'http',
scheme: 'bearer',
2023-11-28 13:41:14 +08:00
})
2023-11-21 15:58:48 +08:00
```
And setup the security scheme for specific routes:
```ts
const route = createRoute({
// ...
security: [
{
Bearer: [],
},
],
})
```
2023-11-28 13:41:14 +08:00
### How to access context in app.doc
You can access the context in `app.doc` as follows:
```ts
2024-05-15 09:06:11 +08:00
app.doc('/doc', (c) => ({
2023-11-28 13:41:14 +08:00
openapi: '3.0.0',
info: {
version: '1.0.0',
title: 'My API',
},
servers: [
{
2023-12-07 08:04:52 +08:00
url: new URL(c.req.url).origin,
2023-11-28 13:41:14 +08:00
description: 'Current environment',
},
],
}))
```
2025-02-28 16:52:52 +08:00
### How to exclude a specific route from OpenAPI docs
You can use `hide` property as follows:
```ts
const route = createRoute({
// ...
hide: true,
})
```
2023-08-27 08:30:05 +08:00
## Limitations
2023-11-20 16:56:04 +08:00
### Combining with `Hono`
2023-09-12 07:25:41 +08:00
Be careful when combining `OpenAPIHono` instances with plain `Hono` instances. `OpenAPIHono` will merge the definitions of direct subapps, but plain `Hono` knows nothing about the OpenAPI spec additions. Similarly `OpenAPIHono` will not "dig" for instances deep inside a branch of plain `Hono` instances.
2023-08-27 08:30:05 +08:00
2023-09-12 07:25:41 +08:00
If you're migrating from plain `Hono` to `OpenAPIHono` , we recommend porting your top-level app, then working your way down the router tree.
2023-08-27 08:30:05 +08:00
2023-11-20 16:56:04 +08:00
### Header keys
Header keys that you define in your schema must be in lowercase.
```ts
const HeadersSchema = z.object({
// Header keys must be in lowercase, `Authorization` is not allowed.
authorization: z.string().openapi({
example: 'Bearer SECRET',
}),
})
```
2023-08-19 09:31:58 +08:00
## References
- [Hono ](https://hono.dev/ )
- [Zod ](https://zod.dev/ )
- [Zod to OpenAPI ](https://github.com/asteasolutions/zod-to-openapi )
## Authors
2023-08-19 01:43:36 +08:00
2023-08-19 09:31:58 +08:00
- Yusuke Wada < https: // github . com / yusukebe >
2023-08-19 01:43:36 +08:00
## License
MIT