💼

【dnt】Hono RPCのAPIスキーマをnpmパッケージ化する

2024/09/22に公開

はじめに

サーバーサイドを Deno、クライアントサイドを Node といった技術スタックでリポジトリを分けて開発する際に、Hono RPC の API スキーマ共有方法が課題になりました。
まずは Git Submodule での解決を目指しましたが、

  • リポジトリの複雑化&管理の難しさ
  • Path Alias の回避
  • ランタイムの違い

が課題になりました。
その中でdntを使い、サーバーサイドの API スキーマを npm パッケージ化し、クライアントサイドでそのパッケージを利用することで解決できたので、この記事ではその手法を紹介します。

dnt とは

dntnpm パッケージを Deno で作成するためのライブラリです。

https://github.com/denoland/dnt

本記事では dnt については詳しく説明しないので、詳細は公式ドキュメントを参照してください。

npm パッケージ化の手順

今回の記事では『サーバーサイドの Hono RPC の API スキーマを npm パッケージ化し、ローカルのクライアントサイドプロジェクトで利用できること』をゴールにします。

環境

今回の環境は以下の通りです。

  • deno 1.46.2
  • node 20.17.0
  • npm 10.8.2

パッケージ化するプログラム

今回は以下のサーバーサイドのソースをパッケージ化します。
$/は Path Alias です。

hello-router.ts
import { Hono } from 'hono'
import { z } from 'zod'
import { zValidator } from '@hono/zod-validator'

const hello = new Hono()
  .get(
    '/',
    zValidator(
      'query',
      z.object({
        name: z.string(),
      }),
    ),
    (c) => {
      const { name } = c.req.valid('query')

      return c.json({ message: `Hello, ${name}!` })
    },
  )

export default hello
app.ts
import { Hono } from 'hono'
import helloApp from '$/hello-router.ts'

const app = new Hono()

const routes = app
  .route('/hello', helloApp)

export type AppType = typeof routes
export default app

また、プロジェクトで使用する依存関係はdeno.jsoncに定義している前提です。

deno.jsonc
{
  "imports": {
    "hono": "jsr:@hono/hono",
    "zod": "https://deno.land/x/zod/mod.ts",
    "@hono/zod-validator": "npm:@hono/zod-validator",
  }
}
サーバー起動用のハンドラは別ファイルへ...

サーバー起動用のハンドラは以下のように別ファイルに分けています。

server.ts
import app from '$/app.ts'

Deno.serve(app.fetch)

理由としては dnt でのパッケージビルド時にProperty 'serve' does not exist on type 'typeof Deno'.というエラーが出たためです。
私自身の dnt に対する知識不足でこのエラー回避方法が分かりませんでした...。

今回は Hono RPC の API スキーマの npm パッケージ化が目的なので、このハンドラの部分は省略します。

1. ビルドスクリプトの作成

それではビルドスクリプトを作成します。

まずは dnt をインストールします。

$ deno add @deno/dnt

次にビルドスクリプトを作成します。
今回のパッケージ名はtest-deno-hono-schemaとします。
また、ビルドファイル名はnpm-build.tsにし、npmディレクトリ下にビルド物を出力するようにします。

設定は最低限動くものなので、詳細な設定は dnt のドキュメントを参照してください。

npm-build.ts
import { build, emptyDir } from '@deno/dnt'

await emptyDir('./npm')

await build({
  entryPoints: ['./app.ts'],
  outDir: './npm',
  test: false,
  shims: {
    deno: true,
  },
  importMap: 'deno.jsonc',
  package: {
    // package.json properties
    name: 'test-deno-hono-schema',
    version: Deno.args[0],
    description: 'Test Deno Hono Schema.',
  },
})

準備は完了です。

2. npm パッケージのビルド

それでは npm パッケージをビルドします(バージョンは0.1.0にしています)。

$ deno run -A npm-build.ts 0.1.0

ビルドに成功するとnpmディレクトリが作成され、その中に npm パッケージが出力されます。
このディレクトリをクライアントサイドのプロジェクトに配置します。

3. 作成した npm パッケージをクライアントサイドで使用する

クライアントサイドのプロジェクトにて、作成した npm パッケージをインストールします。

$ npm install ./npm

あとはクライアントサイドのプロジェクトでインポートして使用するだけです。

client.ts
import { hc } from "hono/client";
import type { AppType } from 'test-deno-hono-schema'

const client = hc<AppType>("http://localhost:8080");

const res = await client.hello.$get({ query: { name: "world" } });
if (res.ok) {
  const json = await res.json();
  console.log(json.message);
}

おわりに

以上が dnt を使って Hono RPC の API スキーマを npm パッケージ化し、クライアントサイドで利用する方法を紹介しました。
今回はローカル環境での利用を前提にしていますが、CI/CD で npm パッケージのビルド等を行っても良さそうです。

参考記事

https://github.com/denoland/dnt

https://zenn.dev/cybozu_frontend/articles/deno-npm-pacakge-dnt#deno-でテストを実装

https://hono.dev/docs/guides/rpc

Discussion