2 ポイント 投稿者 GN⁺ 2023-08-13 | 1件のコメント | WhatsAppで共有
  • tRPCはフルスタックのTypeScriptアプリで TypeScript推論 を共有し、サーバーとクライアント間のAPIの型不一致を減らすためのツール
  • サーバーAPIを変更すると、クライアントコード側で即座に TypeScriptエラーと自動補完 によって影響を確認でき、リファクタリングの負担を減らせる
  • 別個のスキーマや コード生成 の工程なしで動作し、ビルド手順やランタイム負荷を増やさずにタイプセーフなAPIを構成できる
  • React、Next.js、Express、Fastify、AWS Lambda、Solid、Svelte など、さまざまな JavaScript環境 に組み込めるアダプターを提供する
  • 基本的な流れは、プロシージャの定義、HTTPサーバーの作成、AppRouter 型を渡したクライアント接続であり、APIサーバーコードをSDKのように使う開発体験を目指している

tRPCが変えるAPI開発のやり方

  • tRPCはフルスタックアプリケーションで、サーバーとクライアントが同じ TypeScriptの型情報 を活用できるようにし、生産性を高める
  • 中核にあるのは、エンドツーエンドでタイプセーフなAPIを簡単に作り、従来のAPIレイヤーに伴う繰り返し作業を減らすこと
  • サーバー側の変更がクライアントの利用箇所に影響すると、TypeScriptがエラーを通知するため、クライアント・サーバー境界の型不一致をすばやく確認できる

開発者が実感する主な特徴

  • 自動的な型安全性

    • サーバー側の変更がクライアントコードに影響すると、TypeScriptエラーとして表れる
  • 迅速な開発者体験

    • tRPCにはビルドやコンパイルのステップがない
    • コード生成、ランタイム負荷、追加のビルド手順なしで使える
  • フレームワーク非依存

    • JavaScriptフレームワークやランタイム全般で利用できる
    • 既存プロジェクトにも簡単に追加できる
  • 自動補完

    • APIサーバーコードをSDKのように扱える体験を提供する
    • エンドポイント利用時に型ベースのヒントを得られる
  • 小さなクライアントフットプリント

    • tRPCは依存関係がなく、クライアント側のサイズが小さい
  • 同梱アダプター

    • React、Next.js、Express、Fastify、AWS Lambda、Solid、Svelte など向けのアダプターを提供する

基本的な利用フロー

  • tRPC APIはまず プロシージャ(procedure) を定義するところから始まる
  • プロシージャはバックエンドを構成する関数で、組み合わせ可能であり、クエリ、ミューテーション、サブスクリプションとして作成できる
  • 複数のプロシージャはルーターにまとめられる
  • プロシージャの定義

    • 例では initTRPC.create() でtRPCインスタンスを作成し、routerpublicProcedure を構成する
    • greeting プロシージャは name 文字列を入力として受け取り、Hello ${input.name} 形式の文字列を返す
    • 入力検証には Zod を使い、クライアント入力がプロシージャの期待する形式と正確に一致するようにする
    • ファイル末尾で export type AppRouter = typeof appRouter; によりルーター型をエクスポートし、フロントエンドコードで使えるようにする
  • HTTPサーバーの作成

    • createHTTPServerappRouter を渡してtRPCサーバーを実行する
    • 例ではAPIがポート 3000 でリッスンするよう listen(3000) を呼び出す
    • tRPCは Next.js、Express、Fetch APIベースの環境、Fastify、AWS Lambda、vanilla Node HTTPサーバー向けのアダプターを提供する
    • Fetch APIベースの環境の例として、Astro、Remix、SvelteKit、Cloudflare Workers が含まれる
  • クライアント接続とクエリ実行

    • サーバーが起動したら、createTRPCClient<AppRouter> でクライアントを作成し、データをクエリできる
    • 例のクライアントは httpBatchLink を使って http://localhost:3000 に接続する
    • trpc.greeting.query({ name: 'John' }) のように呼び出して、サーバーの greeting プロシージャを利用できる
    • クライアント作成時に AppRouter 型を渡すと、バックエンドAPIと一致した TypeScriptの自動補完 と IntelliSense をコード生成なしで得られる

スタート用テンプレートと制作意図

  • すぐに試せるテンプレートとして Use this template が提供されている
  • tRPCの作者は、従来のAPIレイヤーの必要性を減らし、素早く反復開発しながらもアプリが壊れないという確信を与えるためにtRPCを作った
  • tRPCは先進的な技術チームや複数のFortune 500企業で使われている

開発者の反応

  • 複数の開発者コメントでは、tRPCはコード品質、提供スピード、開発者満足度、クライアント・サーバー境界のリファクタリング、入力検証、型付きミドルウェア体験を改善するツールとして評価されている
  • TypeScriptモノレポでは、素のRESTやGraphQLよりもシンプルでありながら強い型を提供する選択肢として受け止められている
  • Stripe APIのペイロードをサーバーから返しても、Reactコンポーネント側でレスポンスデータの型を受け取れるユースケースが紹介されている

1件のコメント

 
GN⁺ 2023-08-13
Hacker Newsの意見
  • いまのコードベースから tRPCの削除を進めているところだが、強い結合のせいで悪夢のようだった
    ジュニア開発者がインターフェースやデータアクセスパターンを考えないように誘導してしまう面もあり、Prismaからコンポーネントまでそのままつながるマッピングができてしまった
    迅速なプロトタイピングには素晴らしいが、後でコードベースを分離しようとすると、すぐに行き止まりに閉じ込められる

    • スキーマが持つ見落とされがちな利点はまさにこれ。GraphQLのようなスキーマは、データ型と契約について考えさせ、コードの別の部分を担当する人たちが足並みをそろえて作業しやすくしてくれる
      TypeScript以外の言語にも拡張できるので、バックエンドを別の言語へ移したり、SwiftやKotlinのようなネイティブモバイルクライアントを作ったりするときにも役立つ
    • 会社でtRPCを導入したが、少しだけ事前に設計したことで、コード内で「強結合」になりそうなものをうまく分離して使えている
      tRPCは素晴らしいが、結局はバックエンドとフロントエンドの間のトランスポート層にすぎない
      tRPCの内部構造をビジネスロジックの奥深くで使わせるのは、入力、スキーマ、分離を明確に定義するコントローラーやルーター層がないのと同じくらい悪い
      だから後でtRPCから離れるとしても比較的簡単そうだし、たとえばサブシステム全体をキュー上で動くように移すのも難しくなさそうだ
    • 問題はtRPCではなく、型を単に型を付けるためだけに使うエンジニアがいることに近い。どんなツールを使っても同じ問題は起きるはず
      こういうものには学習曲線があり、たいていは type FunctionIWroteTodayArgs = … のような、役に立たず何も説明しない型から始まる
      何度か反復するうちに、目的はコードを複製することではなく、ドメインを説明し、再利用可能で有益な型、インターフェース、APIを作ることだと徐々に気づく
      だからtRPCを取り除くより、チームと一緒にその部分を改善するのが正しいと思う
    • 結合が悪夢だという点をもう少し説明してほしい
      サーバー側でHTTPクライアントを宣言し、クライアント側でそれを利用することが、なぜより悪いのかよく分からない
      私たちはサービスを作り、WebインターフェースやCLIのようなすべての利用者が使うパターンを採っているが、こうしたものが壊れないという点は、以前に見たどの方式よりも大きな改善だった
    • 小さなチームではまったく逆の経験をしているし、以前いた大きなチームでもうまく機能したと思う
      ジュニア開発者がインターフェースを考えないという不満は、tRPCに限らずどんなAPIを使っても同じように出る可能性が高い
  • NotionではtRPCに似たAPIスタイルを使ってきており、そのAPIはtRPCより4年ほど前から存在していた
    TypeScriptのマップ型を使えば、こういうものは簡単に自作できる。キーをAPI名、値を { request, response } 型にしたオブジェクト型を作ればよい
    サーバー側は各APIハンドラーを APIs["addUser"]["request"] を受け取り Promise を返す関数として定義し、クライアント側は同じ引数と戻り値の型を持つ非同期関数として公開すればよい
    この戦略を、HTTPSの内部API、WebSocketベースのリアルタイムAPI、ElectronとWebview間のIPC、iOS/AndroidネイティブとWebview間のOS WebView IPCに使っている
    ネイティブAPIではサーバー側がSwiftやKotlinなので、リクエスト/レスポンス型をTypeScriptで手書きし直している。いつかは独自IDLを持つバイナリ形式に変えるだろうが、ゆっくり大きくなる単一のクロス言語APIには、Protobufのようなものの開発者体験上のコストはまだ見合うようには見えなかった

    • 現在のプロジェクトはTypeScriptフロントエンドとPythonバックエンドの組み合わせで、OpenAPIスキーマを信頼できる唯一の情報源とし、クライアント側にはopenapi-typescript-codegen [0]を使っている
      完璧ではないが、リクエスト/レスポンス型付きのかなり良いAPIインターフェースを提供してくれる
      さらに mockApi((request) => response) のように呼び出せる10行ほどのモックAPIラッパーを作った。モック関数がAPIを正しく実装しているかを型チェックし、実際のAPI関数とまったく同じ見た目の関数を返す
      [0]: https://github.com/ferdikoomen/openapi-typescript-codegen
    • この方式が正しい。tRPCは単に型を推論する以上の不要な複雑さを加えている
      こうしたアプローチを採用した、よく管理され宣伝もされているライブラリがまだ出ていないので、あまり議論されていないのだと思う
    • この過程でリクエストデータの検証はどうしているのか、コツがあるなら知りたい
      tRPC、JSON Schemaと型生成、Zodのようなものを使うときに重要な価値はまさにその部分だ
    • サンプルコードを見ると、XHR呼び出しの上に型をかぶせたラッパーに近いのか気になる
      呼び出し時にジェネリック引数を自分で渡せとまで言っているが、依存関係ツリーを整理された状態に保とうとするとかなり微妙だ
    • この方式は実際かなりうまく動く
      https://github.com/mikew/transmission-material-ui/blob/maste...
      https://github.com/mikew/transmission-material-ui/blob/maste...
  • tRPC がとても好き。TypeScript 専用スタックで開発者体験を限界まで押し上げた点は驚くべきもので、GraphQL コミュニティにクエリ言語の限界とトレードオフを認識させた
    同時に tRPC は本当に速く流行のサイクルを通り過ぎて、REST や GraphQL から RPC へ大規模な移行が起きているようには見えない
    ただ、最近は RPC への関心は高そうで、私たちが作った BFF フレームワーク(https://wundergraph.com/)でも tRPC と以前の NextJS のアイデアを一部採用し、ファイルベースルーティングと RPC を組み合わせた
    tRPC に加えて各オペレーションごとに JSON Schema を自動生成し、オペレーション全体の集合に対する OpenAPI 仕様も生成する
    RPC エンドポイントのまとまりを OpenAPI 仕様や Postman コレクションとして簡単に共有できるので、このアプローチは好まれている。HTTP メソッドについての議論もほとんどなく、実質的にはクエリ、変更、サブスクリプションだけがある
    最近 GraphQL、REST、RPC スタイルの API をどう使っているのか、何人・何チームが API に関わっているのか気になる

    • Garph は GraphQL 向けの tRPC のようなもの: https://garph.dev
      REST API 側には ts-rest(https://ts-rest.com)、zodios(https://www.zodios.org)、Hono(https://hono.dev)がある
      チームで複数の言語を使っているなら Fern もある: https://www.buildwithfern.com
    • 「GraphQL コミュニティにクエリ言語の限界とトレードオフを認識させた」という部分をもう少し説明してほしい
      私たちは連合スキーマが変わるたびに GraphQL 型を生成し、ファイルを保存するたびにクエリと変更のレスポンス型を生成している
    • いつも目につくのは、メソッドとパスがかなり些細な詳細だという点
      エンドポイントのコンシューマ生成ライブラリで簡単に抽象化することもできるが、それ以外に一般的な Web API と比べて実際に何が得られるのか分からない
      いずれにせよ各バックエンド呼び出しでやるべきことはそのまま実行しなければならない
  • tRPC が好き。これまで見たフルスタック開発者体験の中で断然最高で、特に Zod と一緒に使うと API が本当に素晴らしい
    Zod と tRPC は TypeScript の未来における重要なプロジェクトだと見ていて、今後数年で TypeScript エコシステム全体に tRPC に着想を得た開発者体験が大きく花開くと思う
    すでに tRPC の DNA がはっきりしているプロジェクトとしては、別のユースケースを狙う Ping の UploadThing(https://github.com/pingdotgg/uploadthing)と、私たちの Lusat(https://github.com/lusatai/lusat)がある

    • Zod はあまり好きではない。型とジェネリックパラメータが扱いにくく冗長で、オブジェクトのジェネリックにデータ型を渡せず、Zod 自体のスキーマ型をプロパティとして渡さなければならないので、ごちゃごちゃして直感的ではない
      また、検証エラーの型がどのスキーマを検査するかによって変わる点も面倒。エラー処理が予測不能になり、例外的なケースが常に多すぎる
      改善の余地が大きい
    • 最近個人プロジェクトで tRPC と Zod を使ったが、本当に素晴らしい体験だったという点には同意する
      ユニットテストを書くのもずっと楽になった
    • MongoDB 側にこの領域のツールがあるのか気になる
      大きな問題は型安全な CRUDとデータマイグレーションだと思うが、Mongoose はよく言及されるものの、型安全性の面では Zod/TypeScript より大きく後退する感じがする
    • Zod は本当にものすごく良い
      今は少し大げさに言いたい気分だが、特定のプログラミングスタイルでは、コードベースに Zod を追加することは TypeScript を追加するのと同じくらい大きな利点がある、とまで言える
    • effect/core と effect/schema もおすすめ
      スキーマから始めて型付きサービスを作るパターンは、関数型プログラミング寄りの人たちによく合う
  • tRPC がバージョン差とマイグレーションをどう扱うのか気になる
    フィールドにはライフサイクルがある。最初に導入されるときは、どのクライアントもサーバーもそのフィールドを知らない
    クライアントとサーバーは一度にすべて再起動されるわけではなく、同じバージョンの型を持っているわけでもない
    データはあるバージョンの型で書かれ、別のバージョンの型で読まれることになる
    すべてのバイナリ、実行中のプログラム、データがアップグレードされると保証でき、永続データがないなら問題ではないかもしれない
    しかし複数の組織が絡む瞬間、すべてのアプリとサーバーが最新のライブラリバージョンに同期され、再ビルドされ、再デプロイされると保証するのは難しい
    静的型検査はバージョン差がないと仮定する。バイナリ内のすべてのコードは、型を定義する同じバージョンのライブラリでビルドされた閉じた世界である

    • ほとんど「自分でやるしかない」に近い
      Zod は未知のフィールドを許可でき、その後オプションフィールドへ移行し、状況が安定したら必須フィールドにできる
      もちろん、tRPC にやってほしいと最も望む層ほど、こういう種類の問題を考える可能性は最も低い
    • だから Web アプリ向けのツールなのだ
  • 1つの言語だけを対象にするなら、スキーマやコード生成が不要なのは当然

  • tRPC を約5万行の Web アプリケーション2つで使っていて、とても気に入っている。開発者体験が素晴らしい
    ただし tRPC の流行期はすでにしばらく前に過ぎており、RSC の流行がすぐに追いついた感じがする
    最近は tRPC のように安定していて良い解決策があるのに、みんな RSC を使うかどうかの話ばかりしている
    RSC に反対しているわけではないが、議論が多すぎる。最近アプリケーションを作るとき、tRPC はとても実用的な方法だ
    追記: RSC は React Server Components のことで、独自の読み書きデータ哲学も伴っている

    • RSC は聞いたことがなく、検索してもうまく見つからない。リンクがあるとありがたい
    • Royal Shakespeare Company が RPC と何の関係があるのか分からない
      人々が知らないかもしれない略語は、定義するかリンクを付けてほしい
  • いくつかの個人プロジェクトで tRPC と Next.js を使ってみたが、良い体験だった
    特に Create T3 App(https://create.t3.gg/) のような事前設定済みテンプレートと一緒に使うと、イテレーション速度ではなかなか勝てない

    • Next 13 とサーバーコンポーネントでも、今はきちんと動作するのか気になる
  • 一緒に働いていた開発者が tRPC を絶賛するまでは知らなかったが、知ってからはあまりにも明らかに良いものに見えた
    一緒に T3 アプリ(tRPC、Next.js、Tailwind、TypeScript、Prisma)を作った。見たいならここにある: https://github.com/stytchauth/stytch-t3-example
    TypeScript で作業するとき、型安全な API は本当に大きな助けになる