HonoとCloudflareのサービスでHeadless CMSを作った
作ったと言ってますが、まだまだ開発途中です。ただ、メインの機能は大体実装しました!
TL;DR
- honoとcloudflare workers, pagesの相性は最強レベル
- honoを利用することで、フルスタックアプリケーションのバックエンド部分に変更をほとんど加えないまま、フロントエンド部分を差し替えられる
- honoとvitest、@cloudflare/vitest-pool-workersの組み合わせのDXは素晴らしい
- 実装方法によっては意外とレスポンスが遅い
経緯
ある日Xで
という、HonoをベースとしたHeadless CMSのOSSを見つけました。
HonoとVite, Drizzle, Zodベースでありすごくモダンで触ってみたい!と思い早速試してみました。
ただ、ドキュメントではDBがneonというpostgresベースのDBaaSを紹介されており、自分はここでCloudflare D1を使いたかったため、断念しました。
https://honohub.dev/quickstart#setting-up-hono-hub
ないなら自分で試してみようと思い、1から作ってみることにしました。
github
機能
- 管理画面上でのデータスキーマ作成
- 記事CRUD機能
- ユーザー認証機能(認可処理は未実装)
技術スタック
Hono, Vite, Zodベースは変更せず、Cloudflareのサービスのみで運用できるように技術選定を行いました。
- Cloudflare群
- Cloudflare workers (with service bindings)
- Cloudflare D1 (Sqlite)
- Cloudflare R2
- Cloudflare Pages
- 言語
- Typescript
- SQL
- フレームワーク
- Hono
- Vite
- React Router v7
- ライブラリ
- テストライブラリ
- vitest
- バリデーションライブラリ
- zod
- モノレポツール
- turbo
- リンタ&フォーマッタ
- biome
- DB
- sqlc
- UI
- shadcn/ui
- テストライブラリ
Web API構築はHonoとzodを利用しており、apiのコアの依存パッケージはHonoとzodのみです。
管理画面はRemixで構築しました。開発途中でReact Router v7が安定版リリースしましたので、React Router v7に乗り換えました。UIは主にshadcn/uiを利用しました。
ディレクトリ構造
TurboRepoを利用し、モノレポを採用しています。
.
├── README.md
├── apps
│ ├── admin
│ ├── docs
│ └── sandbox
├── biome.json
├── lefthook.yml
├── package.json
├── packages
│ ├── auth
│ └── core
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
├── renovate.json
└── turbo.json
ポイント
ここまで、概要をざっくり説明してきましたが、今回Honoを採用してみてすごく良かった点を紹介します。
テストが異常に簡単
Hono公式にもドキュメントがあり、セットアップが簡単にできました。
@cloudflare/vitest-pool-workers/configを利用します。
Cloudflare D1等Cloudflareサービスをモックするために以下のようにvitest.config.tsを設定します。
import path from "node:path";
import {
defineWorkersProject,
readD1Migrations,
} from "@cloudflare/vitest-pool-workers/config";
export default defineWorkersProject(async () => {
// Read all migrations in the `migrations` directory
const migrationsPath = path.join(__dirname, "db");
const migrations = await readD1Migrations(migrationsPath);
return {
test: {
globals: true,
setupFiles: ["./test/apply-migrations.ts"],
poolOptions: {
workers: {
singleWorker: true,
wrangler: {
configPath: "./wrangler-test.toml",
environment: "production",
},
miniflare: {
// Add a test-only binding for migrations, so we can apply them in a
// setup file
bindings: { TEST_MIGRATIONS: migrations },
},
},
},
},
};
});
テスト時に使用するwrangler-test.tomlを作成します。
name = "core"
main = "./src/index.ts"
compatibility_date = "2024-01-01"
compatibility_flags = ["nodejs_compat"]
[env.production.vars]
AUTH_SECRET = "test"
JWT_SECRET = "test"
[[env.production.kv_namespaces]]
binding = "KV"
id = "00000000000000000000000000000000"
[[env.production.r2_buckets]]
binding = "R2"
bucket_name = "r2-bucket"
[[env.production.d1_databases]]
binding = "DB"
database_name = "database"
database_id = "00000000-0000-0000-0000-000000000000"
ここまで行うと、vitestでwatchオプションを使用しながら、快適に実装を進めることができます!
フロントエンドとの密な連携を実現しながら、フロントエンドのフレームワークに依存しない
honoのRPCを使用することにより、型安全にAPIにアクセスできます。
しかもhonoはWeb標準に則って実装されており、Web標準のRequest, ResponseをIOにとるサーバーサイドフレームワークであれば簡単に結合できます。
つまり、Next,Remix,Nuxt等のフルスタックフレームワークのサーバーサイド部分でHonoを利用できるということです。
Honoでサーバーサイドを実装することで、型安全なRPCを実現しながらフレームワークを柔軟に差し替えることができ、これは大きなメリットだなと思いました。
原因は調査中だが、意外とレスポンスが速くなかった。
ログイン機能等もhonoで実装したのですが、意外とレスポンスが速くなかったです。これはD1とのやりとりも必要かつキャッシュも使えない?からと思われます。とは言っても許容できないような遅さではないので、時間があったら調査しようと思います。
まとめ
HonoとVite、そしてCloudflareのサービスは非常に相性が良く、特に小規模から中規模のサービス開発において大きな利点があると考えています。
これらを組み合わせることで:
- 開発速度の大幅な向上が見込める
- 各機能間の連携を密に保ちながらも、適度な独立性を維持できるため保守性が高い
- Cloudflareのサービスを活用することでコスト面でも優位性がある
以上の特徴から、実務での採用を検討する価値が高いスタックだと考えています。