14
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

[Deno] FreshでHonoのRPCモードを使う 🍋+🔥

Last updated at Posted at 2023-05-23

🍋Freshについて

FreshはDeno社製のWebフレームワークです。

Next.jsのようなファイルシステムベースのルーティングを採用していますが、デフォルトではPreactによるSSRとなっており、必要な場合のみクライアントサイドJSが配信される「アイランドアーキテクチャ」を採用しています。
また、ルーティングにはWeb標準のRequest/Responseオブジェクトを使用します。

🔥Honoについて

Honoはexpressライクなサーバーサイドフレームワークです。Web標準Request/Responseに対応しており、サーバー/クライアント間で型を共有できる「RPCモード」に対応しています。

FreshとHonoを組み合わせる

FreshではJSONなどのレスポンスを返す際、通常routes/ディレクトリ内のファイルにhandler関数を設定します。

routes/api/hello.ts
import { Handler } from "$fresh/server.ts";

// この部分をHonoに置き換えたい
export const handler: Handler = (req, ctx) => {
  return Response.json({ hello: "world" });
};

このhandler関数の部分をHonoに処理させて、ルーティングをカスタマイズしていきたいと思います。

1. import-mapの設定

Honoを使う前に、import_mapにHonoの読み込み先とバージョンを設定しておきます。
この設定をしておくと、import文にhttps://deno.land/x/hono@v3.2.1/mod.tsと書く代わりに$hono/mod.tsと書くことができます。

import_map.json
  {
    "imports": {
      "$fresh/": "https://deno.land/x/fresh@1.1.5/",
      "preact": "https://esm.sh/preact@10.13.1",
      "preact/": "https://esm.sh/preact@10.13.1/",
      "preact-render-to-string": "https://esm.sh/*preact-render-to-string@5.2.6",
      "@preact/signals": "https://esm.sh/*@preact/signals@1.1.3",
      "@preact/signals-core": "https://esm.sh/*@preact/signals-core@1.2.3",
      "twind": "https://esm.sh/twind@0.16.19",
      "twind/": "https://esm.sh/twind@0.16.19/",
+     "$hono/": "https://deno.land/x/hono@v3.2.1/"
    }
  }

2. Honoで処理したいパスのルーティングを設定する

Freshでは、角括弧([])を用いることで、ワイルドカードを使用したルーティングを設定することができます。

ルーティングの一例
routes/index.tsx          --> / (ルート)にアクセスした時に実行される
routes/hello.tsx          --> /helloにアクセスした時に実行される
routes/users/[name].tsx   --> /users/bob や /users/alice にアクセスした時に実行される
routes/hello/[...path].ts --> /hello/foo や /hello/foo/bar など、hello/以下にアクセスした時に実行される

[...path]のような形式のワイルドカードを使用すると、特定のディレクトリ以下へのリクエストをまとめて受け取ることができます。
これを利用して、今回は/api/以下のパスへのリクエストをHonoで受け取るようにしてみます。コードは以下のようになります。

/routes/api/[...path].tsx (例)
import { Handler } from "$fresh/server.ts";
import { Hono } from "$hono/mod.ts";

// /api/以下へのリクエストは全てこのファイルで受け取る

const app = new Hono().basePath("/api");

// ルーティングの設定
const route = app
  .get("/status", (c) => c.jsonT({ status: "ok" }))
  .get("/hello", (c) => c.jsonT({ hello: "world" }));

export const handler: Handler = (req) => app.fetch(req);
export type AppType = typeof route; // rpcモードを使用するときはこのAppTypeをexportする

ポイントは.basePath("/api")でどのパスが処理の対象になっているのか明示することです。
また、後でRPCモードを使用するため、レスポンスの作成を.jsonT()で行い、最後の行でAppTypeをexportしています。

上のコードを保存した状態でサーバーを起動し、/api/helloにアクセスすると、レスポンスが返ってくることが確認できると思います。

image.png

3. Honoで定義したAPIをRPCモードでクライアント側から呼び出す

Honoにはサーバー側で定義したエンドポイントの型定義を使用して、フロントエンド側のAPI呼び出しに型を付けることができるRPCモードが存在します。今回はこれを使ってみたいと思います。

HonoのRPCモードについては、詳しくは公式ドキュメントをご覧ください。

Freshではクライアント側のコードはislandsディレクトリかcomponentsディレクトリに入れる必要があります。
今回は、islandsディレクトリにStatusコンポーネントを作り、先ほどHonoで作成したAPIのエンドポイントを呼び出していきたいと思います。

RPCモードを使うには、以下のようなコードになります。

islands/Status.tsx
  import { useEffect, useState } from "preact/hooks";
+ import { hc } from "$hono/mod.ts";
+ // サーバー側から必要な型をimportする
+ import type { AppType } from "../routes/api/[...path].ts";

+ // クライアント作成時に`AppType`をジェネリクスで渡す
+ const client = hc<AppType>("/");
+ async function getStatus() {
+   // API呼び出し
+   const res = await client.api.status.$get();
+   return await res.json();
+ }

  export default function Hello() {
    const [message, setMessage] = useState("");

+   useEffect(() => {
+     getStatus().then((data) => setMessage(data.status));
+   }, []);

    return <div>{message}</div>;
  }

コードの説明です。
まず、先ほどroutes/api/[...path].tsで作成したAppType型をimportし、hc<AppType>("/")のようにジェネリクスで渡します。こうすることでクライアントとサーバーで共通の型定義を使用することができます。

+ import { hc } from "$hono/mod.ts";
+ import type { AppType } from "../routes/api/[...path].ts";

+ const client = hc<AppType>("/");

その後、client.api.status.$get()関数を呼び出すとAPI呼び出しが行われます。

+ async function getStatus() {
+   const res = await client.api.status.$get();
+   return await res.json();
+ }

vscodeで確認すると、ちゃんとres.json()の戻り値にサーバー側で定義したものと同じ型が付いていることが分かります。

image.png

以上、FreshとHonoを組み合わせてRPCモードを使う方法の解説でした。

まとめ

  • FreshとHonoはどちらもWeb標準のRequest/Responseオブジェクトに対応しているため、組み合わせることができる
  • Freshの特定のパス以下のルーティングを、Honoに処理させることができる
  • RPCモードを使用して、クライアント側とサーバー側でAPI呼び出しの型定義を共有できる
14
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?