久々の投稿です。
HonoでAPIを作って、Swagger UIで動作確認していきます。
プロジェクト作成
まずは公式ドキュメントに記載の通りにプロジェクト作成をします。
pnpm create hoto@latest todo-app
実行すると、実行環境を選べたりするのですが、今後Next.jsでフロントエンドを作っていく予定なので、Next.jsを選びました。
※デプロイまでしたい場合はVercelを選ぶといいようです。
ディレクトリ構造
プロジェクト内のappディレクトリを示します。
app
┣ api
┃ ┗ [...route]
┃ ┃ ┗ route.ts
┣ layout.tsx
┗ page.tsx
route.ts
そしてこれが初期に生成されてるAPIが実装してあるファイルです。
今回の記事では、とりあえずSwagger UIで動作確認することなので、ここに全部書いていきます
app/api/[...route]/route.ts
import { Hono } from 'hono'
import { handle } from 'hono/vercel'
export const runtime = 'edge'
const app = new Hono().basePath('/api')
app.get('/hello', (c) => {
return c.json({
message: 'Hello Hono',
})
})
export const GET = handle(app)
export const POST = handle(app)
APIの実装
準備
OpenAPIのドキュメント生成に@hono/zod-openapi
を使うので、まずインポートと生成するインスタンスを変更します。
import { OpenAPIHono, createRoute, z } from '@hono/zod-openapi'
// Swagger UIで使用
import { swaggerUI } from '@hono/swagger-ui'
const app = new OpenAPIHono().basePath('/api')
メッセージの取得
色々書いてありますが、ほとんどはドキュメント生成用のものです。
動作としては、posts配列の中身を返すだけです。
z
を使うことでバリデーションを行えます。
// メッセージを格納する配列
let posts: {message: string}[] = []
app.openapi(
createRoute({
method: 'get',
path: '/messages',
description: "メッセージを取得する",
responses: {
200: {
description: 'Respond messages',
content: {
'application/json': {
schema: z.array(z.object({
message: z.string()
}))
}
}
}
}
}),
(c) => {
return c.json(posts)
}
)
メッセージの投稿
先述のposts配列に格納するものです。
GETメソッドは異なり、request
という項目があり、posts配列の要素の型と同じになっています。
app.openapi(
createRoute({
method: 'post',
path: '/messages',
description: "メッセージを投稿する",
request: {
body: {
required: true,
content: {
'application/json': {
schema: z.object({
message: z.string()
})
}
}
}
},
responses: {
200: {
description: 'メッセージを投稿しました',
content: {
'application/json': {
schema: z.object({
message: z.string()
})
}
}
},
}
}),
(c) => {
const newMessage = { message: c.req.valid
('json').message }
posts = [...posts, newMessage]
return c.json(newMessage)
}
)
ドキュメントとSwaggerの公開
doc
メソッドでドキュメントを公開します。
ローカルで起動して、/api/docにアクセスするとjson形式のドキュメントが確認できると思います。また、/api/swaggerではSwagger UIで動作確認が行えます。
app.doc('/doc', {
info: {
title: 'An API',
version: 'v1'
},
openapi: '3.1.0'
})
app.get('/swagger', swaggerUI({ url: '/api/doc' }))
まとめ
APIの実装からドキュメントの自動生成、Swagger UIへのAPI公開まで一連の流れを学習できました!(cursor様様ではありますが笑)
実務でもモヤモヤしてたところなので、活かせそうです。
疑問なのは、ドキュメント生成は楽だけど、これをエンドポイントごとに書くのも大変じゃないかなって思いました。