この記事は株式会社エス・エム・エス Advent Calendar 2024の12月12日の記事です。
エス・エム・エスで人材紹介の社内基盤開発をしている熊谷です。 今回はそんな社内システムの1つで、Honoを使って開発している事例をご紹介します。
なぜ私たちはHonoを採用したのか?
人材紹介事業では、CP(キャリアパートナー)と呼ばれる、従事者様の転職のお手伝いをサポートする人員がいます。 従事者様とCPとのコミュニケーションは一般的に電話やメールなどで行われますが、LINEもよくコミュニケーションによく使われる手段の1つです。
従事者様がエス・エム・エスが展開している公式LINEに登録すると、公式LINE上で従事者様専任に割り振られたCPと従事者様との1to1のコミュニケーションが可能になります。 これらを実現するために、LINE Messaging APIを使った中間のWebサーバを社内で管理しています。
元々これらの仕組みは、EC2 + PHPという組み合わせで作られていたのですが、
- コミュニケーション量が増えるに伴って費用も増えてきた
- 処理としては簡易で、EC2で稼働させる必要性がない
- 時間帯や曜日によって負荷に増減があるため、スケールしやすい基盤が向いている
という事情から、Lambda + Node.jsで書き換える企画がスタートしました。
このWebサーバの要件をざっくり伝えると
- シンプルだけど10ページ分のWebページ(フォーム画面)が必要
- Cookieを使う(サーバサイド処理が必要)
というもので、色々なフレームワークを比較検討した結果、Honoが良さげだぞという結論に至りました。 決定打としては「jsxがテンプレートとして使えること」「Ryan Dahl氏がDeno FestでHonoを推してたこと」の2つでした。
採用してみてどうだったか?
非常に良い!!!
というのが感想です、私たちが開発していて実際に良かった点を列挙していきます。
テストが書きやすい
Web Standard APIのRequest/Responseを利用しているからこそ、リクエスト/レスポンスのテストの書きやすさが素晴らしいです
/** * index.ts */ import { Hono } from "hono"; const app = new Hono(); app.get("/hello/:name", (c) => { const name = c.req.param("name"); return c.text(`Hello, ${name}`); }); export default app;
/** * indx.test.ts */ import app from "./index"; test("GET /hello/:name すると、'Hello, ${name}' が返ってくる", async () => { const path = "kumagai"; // app.request()で定義済みルートへアクセスでき、Responseオブジェクトが返却される const res = await app.request(`/hello${path}`); expect(res.status).toBe(200); expect(res.text()).toBe(`Hello, ${path}`); });
zod + reuqest parameter validation
zodを使ったランタイムバリデーションがリクエストパラメータ等の検証に簡単に適合できるのが良いです
const schema = z.object({ id: z.string().uuid(), }); app.post("/entry", zValidator("form", schema), (c) => { c.status(201); return c.text("Created"); });
test("POST /entry は、FormDataでid = uuidのみ受け付ける", async () => { const body = new FormData(); // UUID以外はBadRequest body.append("id", "abc12345"); const res1 = await app.request("/entry", { method: "POST", body }); expect(res1.status).toBe(400); // UUIDは201 body.append("id", "fc56abe1-d352-691e-bd8e-13102bf17549"); const res2 = await app.request("/entry", { method: "POST", body }); expect(res2.status).toBe(201); expect(await res2.text()).toBe("Created"); });
jsxテンプレート
jsxで直感的にフロントエンドが書けるのが素晴らしすぎる
/** * todo.tsx */ import { Hono } from "hono"; import { jsxRenderer } from "hono/jsx-renderer"; import type { FC } from "hono/jsx"; const app = new Hono(); app.use( "/todo/*", jsxRenderer(({ children }) => { return ( <html lang="ja"> <title>My ToDo</title> <body>{children}</body> </html> ); }), ); const ToDoList: FC<{ items: string[] }> = ({ items }) => { return ( <ul> {items.map((item) => ( <li>{item}</li> ))} </ul> ); }; app.get("/", (c) => { return c.render( <> <h1>今日のToDoは?</h1> <ToDoList items={["掃除", "昼寝", "歯磨き"]} /> </>, ); }); export default app;
最後に
私たちがHonoを使っていて、とても感じるのは
「Honoって楽しい!!!」
です。チームとして迷わず開発できている点や、開発生産性も高く、何より使っていて気持ちが良い というのが大きいと思います。
社内のプロダクトで使い始めたHonoですが、個人開発でも何かとHonoをベースに開発する機会が増えました。個人的には Bun + Honoで作ることが多く、TypeScriptやテストの取り回しも楽だし、Cloudflare Workerなどどこでも動くのは本当に魅力だと思っています。
この記事が、皆さんのHono採用きっかけの一助になればと思っています。