ある開発者が、社内ツールのREST APIをtRPCに置き換える作業に2週間を費やしました。
なぜこの移行が重要だったのか
以前のスタックでは、DTO、バリデーター、そしてフロントエンド用の別途TypeScript型定義をすべて維持する必要がありました。バックエンドのフィールド名を変更しても、APIは依然として200を返し続け、UIはレンダリングを続け、バグは単に「undefined」という値として現れるだけでした。チームの誰も気づかないうちに、顧客がその問題を発見してしまったのです。RESTのコントラクトが問題を隠蔽してしまい、余分なファイルが増えることで、見返りのないメンテナンスコストが発生していました。
以前のREST構成
- OpenAPI spec – エンドポイントを記述した静的なファイルですが、決して「信頼できる唯一の情報源(source of truth)」として機能することはありませんでした。
- Client SDK generation step – JavaScriptのラッパーを生成するCIジョブ。
- Postman collection – 誰も使わなくなった、共有されたテスト用アーティファクト。
- Manual TypeScript interfaces – サーバーと同期させ続けなければならない、手書きの型定義。
APIを変更するたびに、これらのアーティファクトのうち少なくとも3つに加え、バリデーターや下流のfetch呼び出しにも影響が及びました。そのプロセスはエラーが起きやすく、時間がかかるものでした。
tRPCがいかにコードベースを削減したか
tRPCは、個別のコントラクトファイルを不要にします。サーバーがrouter型をエクスポートし、クライアントがその同じ型をインポートします。バックエンドのフィールド名を変更すると、エディタが即座に不一致を指摘します。コードを実行したり、リクエストの失敗を確認したりする必要はありません。開発者は以下のものを削除しました:
- OpenAPI specファイル。
- クライアントSDKの生成ステップ。
- 使われていなかったPostman collection。
- 手書きのTypeScript interfaceが入ったフォルダ。
リポジトリはより軽量になり、フィードバックはデプロイ後ではなく、開発中に得られるようになりました。
tRPCの限界
tRPCは、両端がTypeScriptを使用している場合にのみ機能します。以下のような場合には適していません:
- モバイルチームがSwiftやKotlinを使用している場合。
- 外部パートナーがAPIを利用する必要がある場合。
- 言語に依存しない公開インターフェースが必要な場合。
移行に実際に必要だったこと
2週間にわたる作業は、単なるコピー&ペーストではありませんでした。開発者は以下の作業を行う必要がありました:
- すべての
fetch呼び出しを、tRPCのクライアント関数を使用するように書き換える。 - エラーハンドリングのロジックを調整する。
- 生のHTTPレスポンスに紐付いたローディング状態やリトライ状態に依存していたUIコンポーネントを更新する。
導入前に検討すべき質問
- フロントエンドとバックエンドの両方でTypeScriptを使用していますか? 共有された型システムがなければ、tRPCの最大の利点は失われます。
- 両方のサイドを同じチームが担当していますか? 責任範囲にギャップがあると、コントラクトの乖離が再発する可能性があります。
- 具体的なバグを修正しようとしていますか、それともトレンドを追っているだけですか? 生産性の向上は本物ですが、それは既存の課題を解決するためのものであるべきです。
2025年に向けたハイブリッドなアプローチ
多くのチームは、両方のスタックを併用することで最適なバランスを見出しています:
- 社内アプリケーション層にはtRPC:コードベースが完全にTypeScriptであり、同じチームがサーバーとクライアントの両方を管理している場合。
- Webhook、公開API、パートナー連携にはREST:言語に依存しないアクセスが必要な場合。
各接点に対して適切なツールを使用することで、外部の利用者に必要な開放性を維持しつつ、社内開発のスピードを保つことができます。
まとめ: RESTをtRPCに置き換えることで、重複するコントラクトのアーティファクトを排除し、編集時にバグを表面化させることができます。しかし、その恩恵を享受できるのは、スタック全体でTypeScriptを共有しており、かつチームが移行コストを吸収できる場合に限られます。混合戦略をとることで、TypeScript以外の利用者も排除することなく、そのメリットを享受できます。
