2023-08-19 01:43:36 +08:00
|
|
|
# Zod OpenAPI Hono
|
|
|
|
|
2023-08-19 09:31:58 +08:00
|
|
|
**Zod OpenAPI Hono** is extending Hono to support 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).
|
2023-08-19 01:43:36 +08:00
|
|
|
For details on creating schemas and defining routes, please refer to this resource.
|
|
|
|
|
2023-08-19 09:31:58 +08:00
|
|
|
_This is not a real middleware but hosted on this monorepo._
|
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 09:31:58 +08:00
|
|
|
You can install it via the npm. Should be installed with `hono` and `zod`.
|
|
|
|
|
|
|
|
```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 09:31:58 +08:00
|
|
|
#### Write your application
|
|
|
|
|
|
|
|
First, define schemas with Zod:
|
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 01:43:36 +08:00
|
|
|
example: 123,
|
|
|
|
}),
|
|
|
|
name: z.string().openapi({
|
|
|
|
example: 'John Doe',
|
|
|
|
}),
|
|
|
|
age: z.number().openapi({
|
|
|
|
example: 42,
|
|
|
|
}),
|
|
|
|
})
|
|
|
|
.openapi('User')
|
|
|
|
```
|
|
|
|
|
2023-08-19 09:31:58 +08:00
|
|
|
Next, create routes:
|
2023-08-19 01:43:36 +08:00
|
|
|
|
|
|
|
```ts
|
|
|
|
import { createRoute } from '@hono/zod-openapi'
|
|
|
|
|
|
|
|
const route = createRoute({
|
|
|
|
method: 'get',
|
|
|
|
path: '/users/:id',
|
|
|
|
request: {
|
|
|
|
params: ParamsSchema,
|
|
|
|
},
|
|
|
|
responses: {
|
|
|
|
200: {
|
|
|
|
content: {
|
|
|
|
'application/json': {
|
|
|
|
schema: UserSchema,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
description: 'Get the user',
|
|
|
|
},
|
2023-08-19 09:31:58 +08:00
|
|
|
},
|
|
|
|
})
|
|
|
|
```
|
|
|
|
|
|
|
|
Finally, create the App:
|
|
|
|
|
|
|
|
```ts
|
|
|
|
import { OpenAPIHono } from '@hono/zod-openapi'
|
|
|
|
|
|
|
|
const app = new OpenAPIHono()
|
|
|
|
|
|
|
|
app.openapi(route, (c) => {
|
|
|
|
const { id } = c.req.valid('param')
|
|
|
|
return c.jsonT({
|
|
|
|
id,
|
|
|
|
age: 20,
|
|
|
|
name: 'Ultra-man',
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
// OpenAPI document will be served on /doc
|
|
|
|
app.doc('/doc', {
|
|
|
|
openapi: '3.0.0',
|
|
|
|
info: {
|
|
|
|
version: '1.0.0',
|
|
|
|
title: 'My API',
|
|
|
|
},
|
|
|
|
})
|
|
|
|
```
|
|
|
|
|
|
|
|
### Handling validation errors
|
|
|
|
|
|
|
|
You can handle the validation errors the following ways.
|
|
|
|
|
|
|
|
Define the schema:
|
|
|
|
|
|
|
|
```ts
|
|
|
|
const ErrorSchema = z.object({
|
|
|
|
code: z.number().openapi({
|
|
|
|
example: 400,
|
|
|
|
}),
|
|
|
|
message: z.string().openapi({
|
|
|
|
example: 'Bad Request',
|
|
|
|
}),
|
|
|
|
})
|
|
|
|
```
|
|
|
|
|
|
|
|
Add the response:
|
|
|
|
|
|
|
|
```ts
|
|
|
|
const route = createRoute({
|
|
|
|
method: 'get',
|
|
|
|
path: '/users/:id',
|
|
|
|
request: {
|
|
|
|
params: ParamsSchema,
|
|
|
|
},
|
|
|
|
responses: {
|
2023-08-19 01:43:36 +08:00
|
|
|
400: {
|
|
|
|
content: {
|
|
|
|
'application/json': {
|
|
|
|
schema: ErrorSchema,
|
|
|
|
},
|
|
|
|
},
|
2023-08-19 09:31:58 +08:00
|
|
|
description: 'Return Error!',
|
2023-08-19 01:43:36 +08:00
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
```
|
|
|
|
|
2023-08-19 09:31:58 +08:00
|
|
|
Add the hook:
|
2023-08-19 01:43:36 +08:00
|
|
|
|
|
|
|
```ts
|
|
|
|
app.openapi(
|
|
|
|
route,
|
|
|
|
(c) => {
|
|
|
|
const { id } = c.req.valid('param')
|
|
|
|
return c.jsonT({
|
|
|
|
id: Number(id),
|
|
|
|
age: 20,
|
|
|
|
name: 'Ultra-man',
|
|
|
|
})
|
|
|
|
},
|
2023-08-19 09:31:58 +08:00
|
|
|
// Hook
|
2023-08-19 01:43:36 +08:00
|
|
|
(result, c) => {
|
|
|
|
if (!result.success) {
|
2023-08-19 09:31:58 +08:00
|
|
|
return c.jsonT(
|
2023-08-19 01:43:36 +08:00
|
|
|
{
|
2023-08-19 09:31:58 +08:00
|
|
|
code: 400,
|
|
|
|
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-08-19 09:31:58 +08:00
|
|
|
### Middleware
|
|
|
|
|
|
|
|
You can use Hono's middleware as same as using Hono because Zod OpenAPI is just extending Hono.
|
|
|
|
|
|
|
|
```ts
|
|
|
|
import { prettyJSON } from 'hono/pretty-json'
|
|
|
|
|
|
|
|
//...
|
|
|
|
|
|
|
|
app.use('/doc/*', prettyJSON())
|
2023-08-19 01:43:36 +08:00
|
|
|
```
|
|
|
|
|
2023-08-19 09:40:55 +08:00
|
|
|
### RPC-mode
|
|
|
|
|
|
|
|
Zod OpenAPI Hono supports Hono's RPC-mode. You can create the types for passing Hono Client:
|
|
|
|
|
|
|
|
```ts
|
|
|
|
import { hc } from 'hono/client'
|
|
|
|
|
|
|
|
const appRoutes = app.openapi(route, (c) => {
|
|
|
|
const data = c.req.valid('json')
|
|
|
|
return c.jsonT({
|
|
|
|
id: data.id,
|
|
|
|
message: 'Success',
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
const client = hc<typeof appRoutes>('http://localhost:8787/')
|
|
|
|
```
|
|
|
|
|
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
|