🔥

Hono RPC OpenAPI🔥

2024/11/23に公開

Generate RPC code from OpenAPI definition

OpenAPIの定義から、HonoRPCで、使用できるコードを自動生成させてみる。

Demo

OpenAPI YAML

openapi.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

Features

  • 変数の名前をAPIパスに応じて、自動生成。

  • Zodスキーマを自動生成。

  • OpenAPIの定義から、コードを自動生成。

Sample

REST APIを作成する。

App

import { OpenAPIHono } from '@hono/zod-openapi'
import { swaggerUI } from '@hono/swagger-ui'
import { logger } from 'hono/logger'
import { serve } from '@hono/node-server'
import { getRoute, postPostsRoute, getPostsRoute, putPostsIdRoute, deletePostsIdRoute } from '@packages/hono-rpc'
import { getHandler } from '../handler/openapi_hono_handler'
import { postPostsHandler, getPostsHandler, putPostsIdHandler, deletePostsIdHandler } from '../handler/posts_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
      .openapi(getRoute, getHandler)
      .openapi(postPostsRoute, postPostsHandler)
      .openapi(getPostsRoute, getPostsHandler)
      .openapi(putPostsIdRoute, putPostsIdHandler)
      .openapi(deletePostsIdRoute, deletePostsIdHandler)
  }
}

Handler

import { type RouteHandler } from '@hono/zod-openapi'
import { postPostsRoute, getPostsRoute, putPostsIdRoute, deletePostsIdRoute } from '@packages/hono-rpc'
import { Post } from '@packages/prisma'
import { PostsService } from '@packages/service'

export const postPostsHandler: RouteHandler<typeof postPostsRoute> = async (c) => {
  const { post } = c.req.valid('json')
  await PostsService.postPosts(post)
  return c.json({ message: 'Created' }, 201)
}

export const getPostsHandler: RouteHandler<typeof 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)
}

export const putPostsIdHandler: RouteHandler<typeof 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 })
}

export const deletePostsIdHandler: RouteHandler<typeof deletePostsIdRoute> = async (c) => {
  const { id } = c.req.valid('param')
  await PostsService.deletePostsId(id)
  return new Response(null, { status: 204 })
}

The end

HonoRPCは、お気に入りです。

OpenAPIの定義から、Zod OpenAPIを自動生成し、開発を楽にできるようにしたい。🔥

Discussion