Honoを個人開発でフル活用して便利だったところ
はじめに
こんにちは、きうきうと申します。個人開発でいろんなものを作っており、先日、NotionをブログのHeadless CMSにする「NotCMS」をリリースしました。
NotCMSの開発においてHonoをバックエンドとしてフル活用したので、その活用事例に照らして、便利だと思った点・使い方を紹介していきたいと思います。
NotCMSの構成
NotCMSを開発するにあたって、個人開発として持続可能なサービスにすることを技術選定の一つのポイントとしました。
そのため、料金形態が安いCloudflareをインフラとし、それと互換性が高く気になっていた技術である Honoを採用しました。
NotCMSはNotionを簡単に使えて型安全なSDKでアクセスできるようにするというコンセプトですが、そのコンセプト自体、HonoやDrizzleからインスパイアされています。
Honoの推しポイント
1. 型の共有
Honoの一番の特徴であり恩恵はなんと言ってもRPCの型支援にあるでしょう。
バックエンドで hono
、フロントエンドで hono/client
を使い、API仕様を export type AppType = typeof route
で共有することで型安全に開発を行うことができます。
特にReactでは、
import { InferResponseType, hc } from 'hono/client'
import useSWR, { mutate } from 'swr'
export function useVersions(wsId: string, dbId: string) {
const client = hc<AppType>('/')
const $get = client.api.v1.ws[':ws_id'].db[':db_id'].page_snapshots.$get
const $post = client.api.v1.ws[':ws_id'].db[':db_id'].page_snapshots.$post
type PageSnapshot = InferResponseType<typeof $get>['pageSnapshots'][0]
const { data } = useSWR(`/ws/${wsId}/db/${dbId}/page_snapshots`, async () => {
const res = await $get({
param: {
ws_id: wsId,
db_id: dbId,
},
})
return await res.json()
})
const pageSnapshots = data?.pageSnapshots ?? []
const createSnapshots = async (pageIds: string[] | undefined) => {
const res = await $post({
param: {
ws_id: wsId,
db_id: dbId,
},
json: {
pageIds: pageIds,
},
})
if (res.status === 200) {
await mutate(`/ws/${wsId}/db/${dbId}/page_snapshots`)
}
const data = await res.json()
if (data.error != null) {
throw new Error(data.error)
}
return data
}
return {
pageSnapshots,
createSnapshots,
}
}
のようにSWRと組み合わせてfeatureごとにカスタムフックを作成すると見通しが良いでしょう。
また、上記の例の8行目にもある通り、 InferResponseType
を用いれば帰ってくる値の型を単独で取り出して使えるようになり、API側で一度定義すれば型の更新が完結するという体験が得られるのでおすすめです。
2. Cloudflareとの互換性
今回初めてCloudflareをインフラとして採用しましたが、
// env.d.ts
interface CloudflareEnv {
ENV: string
SOME_SECRET: string
DB: D1Database
BUCKET: R2Bucket
}
type Env = {
Bindings: CloudflareEnv
Variables: {
workspace_id: string
}
}
new Hono<Env>()
のようにCloudflareの環境変数を指定したり、Cloudflare D1, R2などをBindingsに指定することが最初から考慮されていて、Honoのおかげで入門が容易でした。
3. Middlewareの定義
Middlewareの定義方法が公式ドキュメントで解説されており、はじめやすかったです。
const authorizeWs = createMiddleware(async (c, next) => {
const db = getDatabase(c.env.DB)
const { secret } = await authorize(c, db)
const workspace_id = c.req.param('workspace_id')
c.set('workspace_id', workspace_id)
c.set('db', db)
await next()
})
protectedRoute.use('/ws/:workspace_id/*', authorizeWs)
ユースケースとしては、このように、認証を行なってユーザーに付随する情報をコンテキストに追加する処理があるルート以下に全て必要な時に、活用できました。
4. OpenAPIがすぐ作れる
3rd-party middlewareを使えば、APIを書いた側からOpenAPIとしてAPI定義を公開することができます(notcms API)。
まとめ
Honoを導入したことで、個人開発で簡単にAPI提供型のサービスを開発することができました。
少し気になった点があるとすれば、Middlewareで登録できる型をEnvのVariablesで指定するが、setする前でも型がついてしまう点と、3rd-partyパッケージのミドルウェアなどが型が不十分だったりする点がありました。しかしそれらの点も、コントリビュートするチャンスであるとも考えられます。Honoは日本発祥でもありコミュニティ活動を始めるのにうってつけだと思いますので、気になる点が少しでもあればPRを積極的に出してみるのもいいでしょう。
ぜひ皆さんもHonoを使って開発してみてください!
Discussion