😚

hono rpc で sveltekit + cloudflare KVでの開発体験がすごく良くなった話

2023/11/14に公開1

始めに

Cloudflare Meet-up や、Workers Tech Talks が開催されたり、Birthday Week での発表で Cloudflare 界隈は盛り上がっていますね。
特に Cloudflare Vectorize が気になっています。

hono をバックエンドの API サーバ として使用し、sveltekit をフロントエンドとして開発を行っています。
hono は cloud run にて動かし、sveltekit を cloudflare pages 上で動かして、バックエンド API のレスポンスをキャッシュとして workers kv に格納するという構成です。
それぞれ別のリポジトリとして開発を行っていましたが、それをモノレポに移した際、hono の prc モードを試したらすごく開発体験が向上したのでその話をします。

hono prc モードとは

簡単にいうと、API サーバのエンドポイントを tRPC のように呼び出せるものです。
tRPC より遥かにカジュアルに使えます。

https://zenn.dev/yusukebe/articles/53713b41b906de#rpcモード

どういうことが良くなったのか

feach をする時に使用するレスポンスに型をつけるための interface の宣言からの解放

これまでは、 エンドポイントの作成 → そのデータを受け取るため interface を作成 → feach という流れで開発をしていました。
それが、エンドポイントの作成 → hono rpcのclientで呼び出す だけになりました。

これによってレスポンスの定義が変わるたびに interface を書き換えるという手間が無くなったので、かなり楽になりました。

また、エンドポイントからのデータを Cloudflare KV に保存する際、json を文字列として保存しています。
以前までは、その文字列を json.parse する際にレスポンスの interface を用いて型付けをしていましたが、hono rpc では、レスポンスの型情報だけの利用もできるので、問題なく同じことが可能です。

以下のようにInferResponseTypeを使うことでエンドポイントの型のみを取得できます。
もちろん、hono 側のコードを変更すると、型の中身も動的に変化します。

type ResponseType = InferResponseType<typeof client.hello.$get>

// vscodeで型を確認すると以下のように確認できる
type ResponseType = {
  message: string
}

DB スキーマの変更時の作業からの解放

バックエンドでは ORM として prisma を使用しています。
フロントの型定義のためにスキーマファイルのみをコピーしてきて、その型情報のみを使用する形をとっていましたが、スキーマ変更した際に忘れがちなprisma generateの実行からの解放という利点もありました。
更新を忘れ動かないストレスが多少ありましたが、 0 になりました。

実例

hono にて rpc を利用するためのコード

基本的には、c.json() としていた部分を、c.jsonT() とするだけです。

import { Hono } from "hono"

const apiApp = new Hono()

const apiRoute = apiApp.get("/hello", (c) => {
  return c.jsonT({
    message: `Hello!`,
  })
})

export default apiApp
export type ApiRoute = typeof apiRoute

クライアントからエンドポイントを呼び出すためのコード

hono の rpc client を宣言するだけで、使用することができます。
vscode の予測変換だけで呼び出すエンドポイントを選択できるため、とても便利です。
注意点としては、別のアプリケーションとして稼働している API サーバの場合、URL を引数で渡す必要があります。

import type { ApiRoute } from "上記のhonoでexportしたApirouteの場所"
const client = hc<ApiRoute>("http://localhost:3000/")
const res = await client.hello.$get()
const data = await res.json()

最終的な sveltekit のコード

Cloudflare KV 上にデータがない場合、エンドポイントへリクエストするコード例です。
KV 上には JSON の文字列が入っているのでパースしてから型をつけますが、それも楽にできています。

import { hc } from "hono/client"
import type { PageServerLoad } from "./$types"
import type { ApiRoute } from "../../../hono/src/index"
import type { InferResponseType } from "hono/client"

export const load: PageServerLoad = async ({ platform }) => {
  const cache = await platform?.env?.MY_KV.get("path")
  if (cache) {
    return {
      tmp: JSON.parse(cache) as InferResponseType<typeof client.hello.$get>,
    }
  }

  const client = hc<ApiRoute>("http://localhost:3000/")
  const res = await client.hello.$get()
  const data = await res.json()
  await platform?.env?.MY_KV.put("path", JSON.stringify(data))

  return {
    tmp: data,
  }
}

まとめ

開発体験がとても良くなりました。
理想としては D1 に完全移行できれば API サーバを用意する必要もないのですが、prisma を剥がす作業と、DB をベクターストアとして使っている都合上移行がめんどくさいですね、、
Cloudflare Vectorize がローカルで使えるようになれば本格的に移行を検討していきたいところです。

GitHubで編集を提案

Discussion