Hono Zod OpenAPI
Zod OpenAPIを使い、REST API
を作成する。
REST API
を設計する際には、各リソース一意なパスで識別しなければいけない。API
の中心にあるのはデータであり、API
を作っているのは、リソース、パラメータ、レスポンス、それらのプロパティ。API
の設計は、一貫性のある名前を選ぶことから始まる。
Under Development
Features
OpenAPIの定義から、HonoのRPCで、使用できるコードを自動生成。
OpenAPI YAML
Details
yaml
info:
title: Hono API
version: v1
openapi: 3.1.0
tags:
- name: Hono
description: Hono API
- name: Post
description: Post API
components:
schemas: {}
parameters: {}
paths:
/:
get:
tags:
- Hono
responses:
'200':
description: Hono🔥
content:
application/json:
schema:
type: object
properties:
message:
type: string
example: Hono🔥
required:
- message
/posts:
post:
tags:
- Post
description: create a new post
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
post:
type: string
minLength: 1
maxLength: 140
required:
- post
responses:
'201':
description: Created
content:
application/json:
schema:
type: object
properties:
message:
type: string
example: Created
required:
- message
'400':
description: Bad Request
content:
application/json:
schema:
type: object
properties:
message:
type: string
example: Bad Request
required:
- message
'500':
description: Internal Server Error
content:
application/json:
schema:
type: object
properties:
message:
type: string
example: Internal Server Error
required:
- message
get:
tags:
- Post
description: get PostList posts with optional pagination
parameters:
- schema:
type: string
required: true
name: page
in: query
- schema:
type: string
required: true
name: rows
in: query
responses:
'200':
description: OK
content:
application/json:
schema:
type: array
items:
type: object
properties:
id:
type: string
format: uuid
post:
type: string
minLength: 1
maxLength: 140
createdAt:
type: string
updatedAt:
type: string
required:
- id
- post
- createdAt
- updatedAt
'400':
description: Bad Request
content:
application/json:
schema:
type: object
properties:
message:
type: string
example: Bad Request
required:
- message
'500':
description: Internal Server Error
content:
application/json:
schema:
type: object
properties:
message:
type: string
example: Internal Server Error
required:
- message
/posts/{id}:
put:
tags:
- Post
description: update Post
parameters:
- schema:
type: string
format: uuid
required: true
name: id
in: path
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
post:
type: string
minLength: 1
maxLength: 140
required:
- post
responses:
'204':
description: No Content
'400':
description: Bad Request
content:
application/json:
schema:
type: object
properties:
message:
type: string
example: Bad Request
required:
- message
'500':
description: Internal Server Error
content:
application/json:
schema:
type: object
properties:
message:
type: string
example: Internal Server Error
required:
- message
delete:
tags:
- Post
description: delete post
parameters:
- schema:
type: string
format: uuid
required: true
name: id
in: path
responses:
'204':
description: No Content
'400':
description: Bad Request
content:
application/json:
schema:
type: object
properties:
message:
type: string
example: Bad Request
required:
- message
'500':
description: Internal Server Error
content:
application/json:
schema:
type: object
properties:
message:
type: string
example: Internal Server Error
required:
- message
Generate
Details
ts
import { createRoute, z } from '@hono/zod-openapi'
export const getRoute = createRoute({
tags: ['Hono'],
method: 'get',
path: '/',
responses: {
200: {
description: 'Hono🔥',
content: {
'application/json': { schema: z.object({ message: z.string() }) },
},
},
},
})
export const postPostsRoute = createRoute({
tags: ['Post'],
method: 'post',
path: '/posts',
description: 'create a new post',
request: {
body: {
required: true,
content: {
'application/json': {
schema: z.object({ post: z.string().min(1).max(140) }),
},
},
},
},
responses: {
201: {
description: 'Created',
content: {
'application/json': { schema: z.object({ message: z.string() }) },
},
},
400: {
description: 'Bad Request',
content: {
'application/json': { schema: z.object({ message: z.string() }) },
},
},
500: {
description: 'Internal Server Error',
content: {
'application/json': { schema: z.object({ message: z.string() }) },
},
},
},
})
export const getPostsRoute = createRoute({
tags: ['Post'],
method: 'get',
path: '/posts',
description: 'get PostList posts with optional pagination',
request: { query: z.object({ page: z.string(), rows: z.string() }) },
responses: {
200: {
description: 'OK',
content: {
'application/json': {
schema: z.array(
z.object({
id: z.string().uuid(),
post: z.string().min(1).max(140),
createdAt: z.string(),
updatedAt: z.string(),
}),
),
},
},
},
400: {
description: 'Bad Request',
content: {
'application/json': { schema: z.object({ message: z.string() }) },
},
},
500: {
description: 'Internal Server Error',
content: {
'application/json': { schema: z.object({ message: z.string() }) },
},
},
},
})
export const putPostsIdRoute = createRoute({
tags: ['Post'],
method: 'put',
path: '/posts/{id}',
description: 'update Post',
request: {
body: {
required: true,
content: {
'application/json': {
schema: z.object({ post: z.string().min(1).max(140) }),
},
},
},
params: z.object({ id: z.string().uuid() }),
},
responses: {
204: { description: 'No Content' },
400: {
description: 'Bad Request',
content: {
'application/json': { schema: z.object({ message: z.string() }) },
},
},
500: {
description: 'Internal Server Error',
content: {
'application/json': { schema: z.object({ message: z.string() }) },
},
},
},
})
export const deletePostsIdRoute = createRoute({
tags: ['Post'],
method: 'delete',
path: '/posts/{id}',
description: 'delete post',
request: { params: z.object({ id: z.string().uuid() }) },
responses: {
204: { description: 'No Content' },
400: {
description: 'Bad Request',
content: {
'application/json': { schema: z.object({ message: z.string() }) },
},
},
500: {
description: 'Internal Server Error',
content: {
'application/json': { schema: z.object({ message: z.string() }) },
},
},
},
})
App
ts
import { OpenAPIHono } from '@hono/zod-openapi'
import { swaggerUI } from '@hono/swagger-ui'
import { logger } from 'hono/logger'
import { serve } from '@hono/node-server'
import { PostsHandler } from './handler/posts_handler'
import { OpenAPIHonoHandler } from './handler/openapi_hono_handler'
export class App {
static init() {
const app = new OpenAPIHono()
const port = 3000
console.log(`Server is running on http://localhost:${port}`)
serve({
fetch: app.fetch,
port,
})
app.use('*', logger())
app.use('*', (c, next) => {
console.log(` ::: ${c.req.method} ${c.req.url}`)
return next()
})
app.use('*', async (c, next) => {
try {
await next()
} catch (e) {
return c.json({ error: (e as Error).message }, 500)
}
})
app
.doc('/doc', {
info: {
title: 'Hono API',
version: 'v1',
},
openapi: '3.1.0',
tags: [
{
name: 'Hono',
description: 'Hono API',
},
{
name: 'Post',
description: 'Post API',
},
],
})
.get('/ui', swaggerUI({ url: '/doc' }))
return this.applyRoutes(app)
}
static applyRoutes(app: OpenAPIHono) {
return app.route('/', OpenAPIHonoHandler.apply(app)).route('/', PostsHandler.apply(app))
}
}
const api = App.init()
export default api
Handler
ts
import { OpenAPIHono } from '@hono/zod-openapi'
import { postPostsRoute, getPostsRoute, putPostsIdRoute, deletePostsIdRoute } from '@packages/hono-rpc'
import { Post } from '@packages/prisma'
import { PostsService } from '@packages/service'
export class PostsHandler {
static apply(app: OpenAPIHono) {
return app
.openapi(postPostsRoute, async (c) => {
const { post } = c.req.valid('json')
await PostsService.postPosts(post)
return c.json({ message: 'Created' }, 201)
})
.openapi(getPostsRoute, async (c) => {
const { page, rows } = c.req.valid('query')
const pageNumber = parseInt(page)
const rowsPerPage = parseInt(rows)
if (isNaN(pageNumber) || isNaN(rowsPerPage) || pageNumber < 1 || rowsPerPage < 1) {
return c.json({ message: 'Bad Request' }, 400)
}
const limit = rowsPerPage
const offset = (pageNumber - 1) * rowsPerPage
const posts: Post[] = await PostsService.getPosts(limit, offset)
return c.json(posts, 200)
})
.openapi(putPostsIdRoute, async (c) => {
const { id } = c.req.valid('param')
const { post } = c.req.valid('json')
await PostsService.putPostsId(id, post)
return new Response(null, { status: 204 })
})
.openapi(deletePostsIdRoute, async (c) => {
const { id } = c.req.valid('param')
await PostsService.deletePostsId(id)
return new Response(null, { status: 204 })
})
}
}