が、断念した。また今度やるかもしれないし、やらないかもしれないので、とりあえずその経緯を書き残しておく。
あらすじ
- APIを実装すると、型付きクライアントが生成される系のソリューションを探してた
- そしてCloudflare Workersでも動いてほしい
まず最初に検討したのがtRPCで、既にCloudflare Workers向けの実装があった。
Cloudflare WorkersでもtRPCを使う | Memory ice cubes https://leaysgur.github.io/posts/2023/08/29/170459/
ただし、現状のtRPCはJSONでしかやり取りする想定がないらしく、バイナリをやり取りできない。
Support for additional
Content-Type
s · Issue #1937 · trpc/trpc https://github.com/trpc/trpc/issues/1937
今の時点では、署名付きURLを発行するなり、tRPCとは別ルートでアップロードする・・とかしかない。
が、tRPCのクライアントとバイナリ用のRESTクライアントの併用なんかやりたくない。
そこで、同様のことができる別のソリューションを探していて、ts-restを見つけた。
ts-rest
ts-rest/ts-rest: RPC-like client, contract, and server implementation for a pure REST API https://github.com/ts-rest/ts-rest
これはどんなものかというと、
- tRPCと同様に、単一の定義からクライアントとサーバーの実装を作る方式
- ts-rest語でContractと呼ぶ
- ただ厳密には、tRPCではその定義が実装そのものである一方、ts-restではただの構造体
- 逆に言うと、実装は別に用意しないといけない
- RESTライクに好きなようにURLを決められる
- OpenAPIの定義も出力できる
という感じ。
今回の経緯でいくと、最大の差別化要因は、multipart/form-data
に対応してる点。
バリデーションがまたしてもzod
一択ってところが個人的にはネック(ランタイムでの使用はマストではないが、内部的な型がどっぷり依存してるとのこと)だが、それはひとまず置いておくとして。
Cloudflare Workersで動かない
問題はコレ。動かないというか、それ用のサーバー側実装がまだない。
- NestJS
- Next.js
- Fastify
- Express
現時点で用意されてるのはこの4つ。
ないなら作ればいい
最初はそう思ってた。
既存のサーバー側実装を見ると、Contractを唯一のソースとする以外は、フォーマットがないというか、それぞれフレームワーク向けによしなにしろって感じだった。
ちなみに、ContractはこういうTSファイル。これはzod
を使ってないバージョン。
import { initContract } from "@ts-rest/core";
const c = initContract();
export const contract = c.router({
createPost: {
method: "POST",
path: "/posts",
responses: {
201: c.type<Post>(),
},
body: c.type<{title: string}>(),
summary: "Create a post",
},
getPost: {
method: "GET",
path: "/posts/:id",
responses: {
200: c.type<Post | null>(),
},
summary: "Get a post by id",
},
});
目をこらすと共通の型みたいなのはあるけど、Fastifyはプラグインとして実装してるがNestJSはデコレーターだったりと、大枠の実装はまちまち。
逆に言えば材料はこれしかないので、
- このオブジェクトの各エントリーをループで回し
router.get("/posts", fn)
みたいな各ルーターのAPIを呼んで登録し- 任意の場所にあるであろうハンドラの実装に対して、検証したInputを渡し
- ハンドラから帰ってきたOutputを検証してレスポンス
というようなコードを用意すればいい。
が、個人的に、もう特定のフレームワークやルーターにどっぷりなものをあまり書きたくないという思いがあり、ここで手を止めた。
ただ特定のフレームワークに依存しないようにしようとすると、どうしてもコードが冗長になってしまう(たとえば、HTTPメソッドとパスの定義が重複する)し、DXもイマイチであまり旨味がないなーとも。
型付きクライアントを出力するのが目的であれば、URLも別にREST風である必要ないよなってことにも気付き、モチベーションが枯れたところ。
ニッチなところを攻めてるな、とは思う。