Hono入門 - @hono/openapi-zodでTodo API開発 -
はじめに
普段はフロントエンドを中心に触れていますが、最近バックエンドにも興味があり、話題のHonoに入門してみました。とりあえずTodo APIの開発をやってみました!
環境構築
公式ドキュメント通りです。テンプレートはcloudflare-workers
を選択しました。
yarn create hono my-app
Module Worker mode
ドキュメントでも言及されていますが、Cloudflare WorkersにはService Worker mode と Module Worker mode というものがあります。今回は登場しませんがBindingsというCloudflareのミドルウェア(KV, R2, D1など)にアクセスするためのものが存在します。Module Worker modeではこのBindingsをローカルスコープで扱えるなどのメリットがあるため基本的には Module Worker modeで使用するのが良いでしょう。
// Module Worker
export default app
スキーマ定義
パッケージ追加
まずは以下を追加します。
Swaggerでドキュメント作成もしたいので@hono/swagger-ui
も追加します。
yarn add @hono/zod-openapi @hono/swagger-ui
次に@hono/zod-openapi
を使ってスキーマを定義していきます。
import { z } from "@hono/zod-openapi";
export const TodoSchema = z
.object({
id: z.number().openapi({ example: 1 }),
title: z.string().openapi({ example: "Learning Hono" }),
completed: z.boolean().openapi({ example: false }),
})
.openapi("TodoSchema");
export const TodoListSchema = z.array(TodoSchema).openapi("TodoListSchema");
// GET, PUT, DELETE用のリクエストモデル
export const TodoParamSchema = z.object({
id: z.string().openapi({ example: "1" }),
});
// Post用のリクエストモデル
export const CreateTodoSchema = z
.object({
title: z.string().openapi({ example: "Learning Hono" }),
})
.openapi("CreateTodoSchema");
エラー用のスキーマも定義しました。
import { z } from "@hono/zod-openapi";
export const MessageSchema = z.object({
code: z.number().openapi({
example: 400,
}),
message: z.string().openapi({
example: "Bad Request",
}),
});
router定義
各エンドポイントの実装をしていきます。(GETとPOST以外は割愛)
@hono/zod-openapi
のcreateRoute
を使用してルート定義をします。
あらかじめファイルは分割しています。分割せずにsrc/index.ts
にルート定義している記事が多かったですが、分割した側をNested route
として追加してあげるといいです。
import { createRoute, OpenAPIHono } from "@hono/zod-openapi";
import { TodoSchema, CreateTodoSchema } from "../models/todos";
import { MessageSchema } from "../models/error";
export const app = new OpenAPIHono();
const todoList = [
{
id: "1",
title: "Learning Hono",
completed: false,
},
{
id: "2",
title: "Implement Todo API",
completed: true,
},
{
id: "3",
title: "Write documentation",
completed: false,
},
];
// GET Todo
const getTodoRoute = createRoute({
method: "get",
path: "/{id}",
request: {
params: TodoParamSchema,
},
responses: {
200: {
content: {
"application/json": {
schema: TodoSchema,
},
},
description: "Get Todo",
},
400: {
description: "Bad Request",
content: {
"application/json": {
schema: MessageSchema,
},
},
},
404: {
content: {
"application/json": {
schema: MessageSchema,
},
},
description: "Not Found",
},
},
tags: ["todo"],
});
app.openapi(
getTodoRoute,
(c) => {
const { id } = c.req.valid("param");
const todo = todoList.find((todo) => todo.id === id);
if (!todo) return c.json({ code: 404, message: "Not Found" }, 404);
return c.json(todo, 200);
},
(result, c) => {
if (!result.success) {
return c.json(
{
code: 400,
message: "Validation Error",
},
400
);
}
}
);
// POST Todo
const createTodoRoute = createRoute({
method: "post",
path: "/",
request: {
body: {
content: {
"application/json": {
schema: CreateTodoSchema,
},
},
},
},
responses: {
200: {
content: {
"application/json": {
schema: TodoSchema,
},
},
description: "Create Todo",
},
400: {
description: "Bad Request",
content: {
"application/json": {
schema: MessageSchema,
},
},
},
},
tags: ["todo"],
});
app.openapi(
createTodoRoute,
async (c) => {
const { title } = await c.req.json();
const newTodo = {
id: String(todoList.length + 1),
title,
completed: false,
};
todoList.push(newTodo);
return c.json(newTodo, 200);
},
(result, c) => {
if (!result.success) {
return c.json(
{
code: 400,
message: "Validation Error",
},
400
);
}
}
);
最後に
import { OpenAPIHono } from "@hono/zod-openapi";
import { app as todoRoute } from "./routes/todo";
const app = new OpenAPIHono();
app.get("/", (c) => {
return c.json({
ok: true,
message: "Hello Hono",
});
});
// Nested routeとしてtodoRouteを追加
app.route("/todo", todoRoute);
export default app;
Swagger UI
Swaggerドキュメント作成のために以下を追加します。
import { OpenAPIHono } from "@hono/zod-openapi";
+ import { swaggerUI } from "@hono/swagger-ui";
import { app as todoRoute } from "./routes/todo";
const app = new OpenAPIHono();
app.get("/", (c) => {
return c.json({
ok: true,
message: "Hello Hono",
});
});
app.route("/todo", todoRoute);
+ app.doc("/doc", {
+ openapi: "3.0.0",
+ info: {
+ version: "1.0.0",
+ title: "My TODO API with Hono",
+ },
+ });
+ app.get("/ui", swaggerUI({ url: "/doc" }));
export default app;
いい感じ!
終わりに
@hono/openapi-zod を使い簡単にスキーマを定義しながらrouerの実装もできました。
まだまだ入門したてなのでこれからどんどん使っていこうと思います!
次回あればDBとの連携もやっていこうと思いますー。
Discussion