parent
5c22474098
commit
6d089751fd
|
@ -1,17 +1,19 @@
|
||||||
# Zod OpenAPI Hono
|
# Zod OpenAPI Hono
|
||||||
|
|
||||||
**Zod OpenAPI Hono** is extending Hono to support OpenAPI.
|
**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).
|
||||||
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).
|
|
||||||
For details on creating schemas and defining routes, please refer to this resource.
|
|
||||||
|
|
||||||
_This is not a real middleware but hosted on this monorepo._
|
_Note: This is not standalone middleware but is hosted on the monorepo "[github.com/honojs/middleware](https://github.com/honojs/middleware)"._
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
- Currently, it does not support validation of _headers_ and _cookies_.
|
||||||
|
- An instance of Zod OpenAPI Hono cannot be used as a "subApp" in conjunction with `rootApp.route('/api', subApp)`.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
### Installation
|
### Installation
|
||||||
|
|
||||||
You can install it via the npm. Should be installed with `hono` and `zod`.
|
You can install it via npm. It should be installed alongside `hono` and `zod`.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
npm i hono zod @hono/zod-openapi
|
npm i hono zod @hono/zod-openapi
|
||||||
|
@ -19,9 +21,9 @@ npm i hono zod @hono/zod-openapi
|
||||||
|
|
||||||
### Basic Usage
|
### Basic Usage
|
||||||
|
|
||||||
#### Write your application
|
#### Setting up your application
|
||||||
|
|
||||||
First, define schemas with Zod:
|
First, define your schemas with Zod. The `z` object should be imported from `@hono/zod-openapi`:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { z } from '@hono/zod-openapi'
|
import { z } from '@hono/zod-openapi'
|
||||||
|
@ -54,7 +56,7 @@ const UserSchema = z
|
||||||
.openapi('User')
|
.openapi('User')
|
||||||
```
|
```
|
||||||
|
|
||||||
Next, create routes:
|
Next, create a route:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { createRoute } from '@hono/zod-openapi'
|
import { createRoute } from '@hono/zod-openapi'
|
||||||
|
@ -72,13 +74,13 @@ const route = createRoute({
|
||||||
schema: UserSchema,
|
schema: UserSchema,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
description: 'Get the user',
|
description: 'Retrieve the user',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
Finally, create the App:
|
Finally, set up the app:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { OpenAPIHono } from '@hono/zod-openapi'
|
import { OpenAPIHono } from '@hono/zod-openapi'
|
||||||
|
@ -94,7 +96,7 @@ app.openapi(route, (c) => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// OpenAPI document will be served on /doc
|
// The OpenAPI documentation will be available at /doc
|
||||||
app.doc('/doc', {
|
app.doc('/doc', {
|
||||||
openapi: '3.0.0',
|
openapi: '3.0.0',
|
||||||
info: {
|
info: {
|
||||||
|
@ -104,11 +106,17 @@ app.doc('/doc', {
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
### Handling validation errors
|
You can start your app just like you would with Hono. For Cloudflare Workers and Bun, use this entry point:
|
||||||
|
|
||||||
You can handle the validation errors the following ways.
|
```ts
|
||||||
|
export default app
|
||||||
|
```
|
||||||
|
|
||||||
Define the schema:
|
### Handling Validation Errors
|
||||||
|
|
||||||
|
Validation errors can be handled as follows:
|
||||||
|
|
||||||
|
First, define the error schema:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
const ErrorSchema = z.object({
|
const ErrorSchema = z.object({
|
||||||
|
@ -121,7 +129,7 @@ const ErrorSchema = z.object({
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
Add the response:
|
Then, add the error response:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
const route = createRoute({
|
const route = createRoute({
|
||||||
|
@ -137,13 +145,13 @@ const route = createRoute({
|
||||||
schema: ErrorSchema,
|
schema: ErrorSchema,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
description: 'Return Error!',
|
description: 'Returns an error',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
Add the hook:
|
Finally, add the hook:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
app.openapi(
|
app.openapi(
|
||||||
|
@ -162,7 +170,7 @@ app.openapi(
|
||||||
return c.jsonT(
|
return c.jsonT(
|
||||||
{
|
{
|
||||||
code: 400,
|
code: 400,
|
||||||
message: 'Validation Error!',
|
message: 'Validation Error',
|
||||||
},
|
},
|
||||||
400
|
400
|
||||||
)
|
)
|
||||||
|
@ -173,7 +181,7 @@ app.openapi(
|
||||||
|
|
||||||
### Middleware
|
### Middleware
|
||||||
|
|
||||||
You can use Hono's middleware as same as using Hono because Zod OpenAPI is just extending Hono.
|
Zod OpenAPI Hono is an extension of Hono, so you can use Hono's middleware in the same way:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { prettyJSON } from 'hono/pretty-json'
|
import { prettyJSON } from 'hono/pretty-json'
|
||||||
|
@ -183,9 +191,9 @@ import { prettyJSON } from 'hono/pretty-json'
|
||||||
app.use('/doc/*', prettyJSON())
|
app.use('/doc/*', prettyJSON())
|
||||||
```
|
```
|
||||||
|
|
||||||
### RPC-mode
|
### RPC Mode
|
||||||
|
|
||||||
Zod OpenAPI Hono supports Hono's RPC-mode. You can create the types for passing Hono Client:
|
Zod OpenAPI Hono supports Hono's RPC mode. You can define types for the Hono Client as follows:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { hc } from 'hono/client'
|
import { hc } from 'hono/client'
|
||||||
|
|
|
@ -177,10 +177,9 @@ describe('Query', () => {
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
const PostsSchema = z
|
const BooksSchema = z
|
||||||
.object({
|
.object({
|
||||||
title: z.string().openapi({}),
|
titles: z.array(z.string().openapi({})),
|
||||||
content: z.string().openapi({}),
|
|
||||||
page: z.number().openapi({}),
|
page: z.number().openapi({}),
|
||||||
})
|
})
|
||||||
.openapi('Post')
|
.openapi('Post')
|
||||||
|
@ -195,10 +194,10 @@ describe('Query', () => {
|
||||||
200: {
|
200: {
|
||||||
content: {
|
content: {
|
||||||
'application/json': {
|
'application/json': {
|
||||||
schema: PostsSchema,
|
schema: BooksSchema,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
description: 'Get the posts',
|
description: 'Get books',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -208,8 +207,7 @@ describe('Query', () => {
|
||||||
app.openapi(route, (c) => {
|
app.openapi(route, (c) => {
|
||||||
const { page } = c.req.valid('query')
|
const { page } = c.req.valid('query')
|
||||||
return c.jsonT({
|
return c.jsonT({
|
||||||
title: 'Good title',
|
titles: ['Good title'],
|
||||||
content: 'Good content',
|
|
||||||
page: Number(page),
|
page: Number(page),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -218,8 +216,7 @@ describe('Query', () => {
|
||||||
const res = await app.request('/books?page=123')
|
const res = await app.request('/books?page=123')
|
||||||
expect(res.status).toBe(200)
|
expect(res.status).toBe(200)
|
||||||
expect(await res.json()).toEqual({
|
expect(await res.json()).toEqual({
|
||||||
title: 'Good title',
|
titles: ['Good title'],
|
||||||
content: 'Good content',
|
|
||||||
page: 123,
|
page: 123,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -236,7 +233,7 @@ describe('JSON', () => {
|
||||||
title: z.string().openapi({}),
|
title: z.string().openapi({}),
|
||||||
})
|
})
|
||||||
|
|
||||||
const PostsSchema = z
|
const PostSchema = z
|
||||||
.object({
|
.object({
|
||||||
id: z.number().openapi({}),
|
id: z.number().openapi({}),
|
||||||
title: z.string().openapi({}),
|
title: z.string().openapi({}),
|
||||||
|
@ -259,10 +256,10 @@ describe('JSON', () => {
|
||||||
200: {
|
200: {
|
||||||
content: {
|
content: {
|
||||||
'application/json': {
|
'application/json': {
|
||||||
schema: PostsSchema,
|
schema: PostSchema,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
description: 'Get the posts',
|
description: 'Post a post',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -304,6 +301,7 @@ describe('JSON', () => {
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
|
body: JSON.stringify({}),
|
||||||
})
|
})
|
||||||
const res = await app.request(req)
|
const res = await app.request(req)
|
||||||
expect(res.status).toBe(400)
|
expect(res.status).toBe(400)
|
||||||
|
@ -316,7 +314,7 @@ describe('Form', () => {
|
||||||
title: z.string().openapi({}),
|
title: z.string().openapi({}),
|
||||||
})
|
})
|
||||||
|
|
||||||
const PostsSchema = z
|
const PostSchema = z
|
||||||
.object({
|
.object({
|
||||||
id: z.number().openapi({}),
|
id: z.number().openapi({}),
|
||||||
title: z.string().openapi({}),
|
title: z.string().openapi({}),
|
||||||
|
@ -339,10 +337,10 @@ describe('Form', () => {
|
||||||
200: {
|
200: {
|
||||||
content: {
|
content: {
|
||||||
'application/json': {
|
'application/json': {
|
||||||
schema: PostsSchema,
|
schema: PostSchema,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
description: 'Post the post',
|
description: 'Post a post',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -393,7 +391,7 @@ describe('Types', () => {
|
||||||
title: z.string().openapi({}),
|
title: z.string().openapi({}),
|
||||||
})
|
})
|
||||||
|
|
||||||
const PostsSchema = z
|
const PostSchema = z
|
||||||
.object({
|
.object({
|
||||||
id: z.number().openapi({}),
|
id: z.number().openapi({}),
|
||||||
message: z.string().openapi({}),
|
message: z.string().openapi({}),
|
||||||
|
@ -416,10 +414,10 @@ describe('Types', () => {
|
||||||
200: {
|
200: {
|
||||||
content: {
|
content: {
|
||||||
'application/json': {
|
'application/json': {
|
||||||
schema: PostsSchema,
|
schema: PostSchema,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
description: 'Post the post',
|
description: 'Post a post',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue