HonoでAPIのドキュメントを自動生成する
はじめに
Honoでは@hono/zod-openapiと@hono/swagger-uiを利用することで、APIのドキュメントを自動生成することができます。
今回は、この2つのパッケージを利用して、APIのドキュメントを自動生成する方法を紹介します。
Honoとは
Honoは軽量かつ、高速な事が特徴なWebフレームワークです。
さらに、特定の実行環境に依存しないように設計されているため、Node.jsやDeno、Cloudflare Workersなど、様々な環境で利用することができます。
また、Honoは様々なMiddleware[1]を提供しており、それらを組み合わせることで、様々な機能を実現することができます。
今回利用する@hono/zod-openapi
と@hono/swagger-ui
もその一つです。
本題
下記のような受け取った入力をそのまま応答するAPIを題材に、APIのドキュメントを自動生成する方法を紹介します。
import { Hono } from 'hono';
const app = new Hono();
app.post('/echo', async context => {
const body = await c.req.json()
return context.json({ result: body.input });
});
export default app;
1. OpenAPIHonoインスタンスを作成する
通常Honoを利用する場合は、hono
パッケージからHono
クラスをインポートし、インスタンスを作成します。
今回は@hono/zod-openapi
を利用するため、@hono/zod-openapi
パッケージからOpenAPIHono
クラスをインポートし、インスタンスを作成するように置き換えます。
import { OpenAPIHono } from '@hono/zod-openap';
const app = new OpenAPIHono();
app.post('/echo', async context => {
const body = await c.req.json();
return context.json({ result: body.input });
});
export default app;
2. OpenAPIのRoute定義を追加する
次に、@hono/zod-openapi
のcreateRoute
メソッドを利用して、OpenAPIのRoute定義を追加します。
import { createRoute, z } from '@hono/zod-openapi';
const route = createRoute({
path: '/echo',
method: 'post',
description: '受け取った入力値をそのまま応答する',
request: {
body: {
required: true,
content: {
'application/json': {
schema: z.object({
input: z.string().openapi({
example: 'Hello World!',
description: '入力',
}),
}),
},
},
},
},
responses: {
200: {
description: 'OK',
content: {
'application/json': {
schema: z.object({
result: z.string().openapi({
example: 'Hello World!',
description: '応答',
}),
}),
},
},
},
},
});
3. Route定義を登録する
app.post
メソッドの代わりにopenapi
メソッドを利用し、先程作成したRoute定義を登録します。
app.openapi(route, async context => {
const body = await c.req.json();
return context.json({ result: body.input });
});
4. RequestBodyの取得方法を変更する
@hono/zod-openapi
を利用する場合、context.req.valid
メソッドを利用してZodで検証済みの値を取得できます。
今回はRequestBodyを取得する為、json
を指定していますが、他にもheader
やquery
なども指定できます。
app.openapi(route, async context => {
const body = await c.req.valid('json');
return context.json({ result: body.input });
});
5. OpenAPIドキュメントを公開する
OpenAPIHono
インスタンスのdoc
メソッドを利用して、OpenAPIドキュメントを公開します。
app
.openapi(...)
.doc('/specification', {
openapi: '3.0.0',
info: {
title: 'API',
version: '1.0.0',
},
});
6. SwaggerUIを公開する
最後に@hono/swagger-ui
を利用することで、Swagger UIを公開することができます。
urlには、先程公開したOpenAPIドキュメントのURLを指定します。
import { swaggerUI } from '@hono/swagger-ui';
app
.openapi(...)
.doc('/specification', ...)
.get('/doc', swaggerUI({
url: '/specification',
}));
以上で、APIのドキュメントをcreateRoute
で定義した情報を元に自動生成されます。
サーバー起動後、/doc
にアクセスすることで、Swagger UIが表示され、APIのドキュメントを確認できます。
コード全文
import { swaggerUI } from '@hono/swagger-ui';
import { createRoute, OpenAPIHono, z } from '@hono/zod-openapi';
const app = new OpenAPIHono();
const route = createRoute({
path: '/echo',
method: 'post',
description: '受け取った入力値をそのまま応答する',
request: {
body: {
required: true,
content: {
'application/json': {
schema: z.object({
input: z.string().openapi({
example: 'Hello World!',
description: '入力',
}),
}),
},
},
},
},
responses: {
200: {
description: 'OK',
content: {
'application/json': {
schema: z.object({
result: z.string().openapi({
example: 'Hello World!',
description: '応答',
}),
}),
},
},
},
},
});
app
.openapi(route, async context => {
const body = await c.req.valid('json');
return context.json({ result: body.input });
})
.doc('/specification', {
openapi: '3.0.0',
info: {
title: 'API',
version: '1.0.0',
},
})
.get('/doc', swaggerUI({
url: '/specification',
}));
export default app;
応用編 (Basic認証を掛ける)
業務で実際に利用する場合、APIのドキュメントは開発者のみが閲覧できるようにしたい場合があります。
そのような場合は、Basic認証を掛けることで、APIのドキュメントを閲覧できるユーザーを制限可能です。
1. SwaggerUIにBasic認証を掛ける
Honoには組み込みのMiddlewareから、Basic認証を掛けるためのbasicAuth
Middlewareが提供されています。
このbasicAuth
Middlewareを利用することで、簡単にBasic認証を掛けることができます。
import { basicAuth } from 'hono/basic-auth';
app
.use('/doc', basicAuth({
username: 'user',
password: 'password',
}));
app
.openapi(...)
.doc('/specification', ...)
.get('/doc', ...);
2. OpenAPIドキュメントにBearer認証を掛ける
SwaggerUIへはBasic認証を掛けることが出来ましたが、このままではOpenAPIドキュメントには認証が掛かっていません。
OpenAPIドキュメントにも認証を掛けるために、同じく組み込みのMiddlewareから、Bearer認証を掛けるためのbearerAuth
Middlewareを利用します。
import { bearerAuth } from 'hono/bearer-auth';
app
.use('/specification', bearerAuth({
token: 'bearer-token',
}))
.use('/doc', ...);
app
.openapi(...)
.doc('/specification', ...)
.get('/doc', ...);
3. SwaggerUIからOpenAPIドキュメントにアクセス出来るようにする
先程、OpenAPIドキュメントへBearer認証を掛けましたが、このままではSwaggerUIからOpenAPIドキュメントにアクセスすることができません。
そのため、SwaggerUIからOpenAPIドキュメントにアクセスできるようにするために、requestInterceptor
を利用します。
requestInterceptor
はSwaggerUIのリクエストを加工するための関数で、ここでBearerトークンをリクエストヘッダーに追加することで、SwaggerUIからOpenAPIドキュメントにアクセスできるようになります。
app
.use('/specification', ...)
.use('/doc', ...);
app
.openapi(...)
.doc('/specification', ...)
.get('/doc', swaggerUI({
url: '/specification',
requestInterceptor: `
request => {
if (request.url === '/specification') {
request.headers['authorization'] = \`Bearer bearer-token\`;
}
return request;
}
`,
}));
コード全文
import { swaggerUI } from '@hono/swagger-ui';
import { createRoute, OpenAPIHono, z } from '@hono/zod-openapi';
import { basicAuth } from 'hono/basic-auth';
import { bearerAuth } from 'hono/bearer-auth';
const app = new OpenAPIHono();
const route = createRoute({
path: '/echo',
method: 'post',
description: '受け取った入力値をそのまま応答する',
request: {
body: {
required: true,
content: {
'application/json': {
schema: z.object({
input: z.string().openapi({
example: 'Hello World!',
description: '入力',
}),
}),
},
},
},
},
responses: {
200: {
description: 'OK',
content: {
'application/json': {
schema: z.object({
result: z.string().openapi({
example: 'Hello World!',
description: '応答',
}),
}),
},
},
},
},
});
app
.use('/specification', bearerAuth({
token: 'bearer-token',
}))
.use('/doc', basicAuth({
username: 'user',
password: 'password',
}));
app
.openapi(route, async context => {
const body = await c.req.valid('json');
return context.json({ result: body.input });
})
.doc('/specification', {
openapi: '3.0.0',
info: {
title: 'API',
version: '1.0.0',
},
})
.get('/doc', swaggerUI({
url: '/specification',
requestInterceptor: `
request => {
if (request.url === '/specification') {
request.headers['authorization'] = \`Bearer bearer-token\`;
}
return request;
}
`,
}));
export default app;
まとめ
ただ軽量なだけでなく、様々なMiddlewareが提供されている為、実際の業務でも利用しやすいフレームワークであると思います。
シンプルなREST APIを構築する際には、Honoはベストな選択肢の一つであると言えるのではないでしょうか。
気になった方は、公式ドキュメントを参照して、実際に試してみてください!
ここまで読んでいただき、ありがとうございました。
-
外部ライブラリに依存しない組み込みのMiddlewareと、外部ライブラリに依存するサードパーティMiddlewareがあります。 ↩︎
Discussion