1️⃣

Next.js/appディレクトリ用のRPCライブラリを作った

2023/05/24に公開

TL;DR

ルーティングは全てフレームワークに任せてエンドポイント一つだけ定義するためのRPCライブラリを作りました。Next.js/appディレクトリで運用しているとき内部APIを作るのに便利です。

https://github.com/raviqqe/oneRPC

背景

Wiktionaryのデータを使って英英辞書のウェブアプリをNext.js/appディレクトリでちょこちょこ作っています。内部APIにはtRPCを使っていましたが、いくつか不満がありました。

  • 基本的に複数のプロシージャをRouterで一つのエンドポイントにまとめる仕様になっているが、Vercelのエッジランタイムで動くような軽いものは別のエンドポイントでデプロイしたい
  • Next.js/appディレクトリのエンドポイントを一つずつ定義するスタイルと相性が悪い
  • ストリーミングレスポンスに対応しておらず、そのようなエンドポイントには生HTTPを使う必要がある

そんな中で以下のような記事を見て、RPCライブラリを自作することにしました。

https://zenn.dev/mizchi/articles/typed-fetch-magic

https://zenn.dev/yusukebe/articles/53713b41b906de

コンセプト

名前はoneRPC。Readmeに書いてある通りですが、

  • ローカルの関数を呼ぶようにサーバサイドの関数をクライアントから呼べる
  • TypeScript向けの型安全なAPI
  • ルーティングは提供しない。フレームワークやインフラに任せる
  • HTTPの機能をなるべく活用する(ヘッダ等)
  • Web APIにしか依存しない。エッジランタイムで動く
  • ストリーミングレスポンスのサポート

上記の記事にあるようなRequest・Response型を直接使うことは諦めました。そのかわりに(input: T) => S型のサーバサイド関数を(request: Request) => Promise<Response>型に変換するだけのミニマルな設計のAPIにしました。

API

今の所query, queryStream, mutate関数があります。それぞれonerpconerpc/clientモジュールから利用可能で、それぞれサーバサイドの手続き定義、クライアントサイドの手続き呼び出しに使います。query*関数はGETメソッドにmutate関数はPOSTメソッドに対応します。Zodか独自の関数で入力と出力の検査ができます。

tRPCにあるような自動バッチは今の所サポートしていません。(サポートしたとしても一つの手続きしかまとめられないので。)

Next.js/appディレクトリでの利用例

app/api/foo/route.ts:

import { query } from "onerpc";
import { z } from "zod";

export const GET = query(z.number(), z.string(), (x) => `Hello, ${x}!`, {
  path: "/api/foo",
});

app/page.tsx:

import { type GET } from "@/app/api/foo/route";
import { query } from "onerpc/client";

export default async (): Promise<JSX.Element> => (
  <div>{await query<typeof GET>("/api/foo", 42))}</div>
);

感想

  • 正直、Next.jsがServer Actionsみたいないい感じAPI出してくれないかな... なんで現状mutationのみのサポートなんだろう。全てサーバコンポーネントにしてということ?
  • Hono.jsがストリーミングとappディレクトリに対応したらそっちにするかも

結論

まあまあ便利。

Discussion