TypeScript tRPC活用法|型安全なAPI開発とフルスタック開発の実践ポイントを解説
TypeScriptを使ったWebアプリ開発では、フロントエンドとバックエンドの型をどのように一致させるかが重要な課題になります。従来のAPI開発では、バックエンド側でAPIを実装し、別途API仕様書を作成し、フロントエンド側でその仕様に合わせて型を定義する流れが一般的でした。しかし、この方法では仕様書の更新漏れや、フロントエンドとバックエンドの型のズレが起こりやすく、開発規模が大きくなるほど保守コストが増加します。
tRPCは、この課題を解決するために使われるTypeScript向けのAPI開発フレームワークです。バックエンドで定義したルーターやプロシージャの型をフロントエンド側でそのまま利用できるため、APIの呼び出し時に入力値や戻り値の型を安全に扱えます。特に、フロントエンドとバックエンドの両方をTypeScriptで開発するプロジェクトでは、tRPCによって型定義の重複を減らし、開発体験を大きく改善できます。
本記事では、TypeScript tRPC活用法として、型安全なAPI開発、フルスタック開発、CRUD機能、認証、Prisma連携、Zodによる入力検証、管理画面構築、リアルタイム通信、リアクトクエリ連携、モノレポ構成、SaaS開発、サーバーアクションとの使い分けまで体系的に解説します。tRPCは便利な一方で、設計を誤るとルーターが肥大化したり、権限管理が複雑になったりするため、導入時には設計方針を明確にすることが重要です。
1. TypeScript tRPCによる型安全なAPI開発
TypeScript tRPCの最大の特徴は、フロントエンドとバックエンドをまたいだ型安全なAPI開発を実現できることです。通常のAPI開発では、バックエンドが返すデータ形式をフロントエンド側で別途型定義しなければならないことがあります。その場合、API仕様が変わったときにフロントエンド側の型更新が漏れると、実行時エラーや表示不具合につながります。tRPCでは、バックエンド側で定義したプロシージャの型情報をフロントエンド側で推論できるため、こうしたズレを減らしやすくなります。
型安全なAPI開発では、単に型が付いているだけでなく、入力値、戻り値、エラー、権限、バリデーションまで一貫して設計することが重要です。tRPCを使うと、APIエンドポイントごとに入力スキーマや処理内容を定義し、その型をクライアント側で自然に利用できます。これにより、API呼び出し時の引数ミス、戻り値のプロパティ名の間違い、不要な型変換を減らせます。
1.1 フロントエンドとバックエンドで型共有
フロントエンドとバックエンドで型を共有できることは、tRPCを採用する大きな理由の一つです。たとえば、バックエンド側でユーザー取得APIを定義すると、その戻り値の型がフロントエンド側に伝わるため、画面側では補完を利用しながら安全にデータを扱えます。ユーザー情報に新しい項目を追加した場合も、型の変更が呼び出し側に反映されるため、修正が必要な箇所に気づきやすくなります。
この型共有は、特にフルスタックTypeScript開発で効果を発揮します。API仕様を別ファイルで二重管理するのではなく、バックエンド実装を型情報の単一ソースとして扱えるため、仕様の一貫性を保ちやすくなります。ただし、すべての内部型をそのままフロントエンドへ公開すればよいわけではありません。データベース内部の型、APIレスポンス用の型、画面表示用の型は目的が異なるため、公開してよい型の範囲を意識する必要があります。
1.2 API仕様書不要の開発フロー
tRPCでは、従来のようにAPI仕様書を別途作成してフロントエンドとバックエンドで突き合わせる作業を減らせます。バックエンド側のルーター定義そのものが型情報を持つため、フロントエンドはその型を参照しながらAPIを呼び出せます。これにより、API仕様書の更新漏れや、仕様書と実装の不一致が発生しにくくなります。
ただし、API仕様書が完全に不要になるという意味ではありません。外部開発者にAPIを公開する場合、他言語のクライアントが利用する場合、非エンジニアの関係者に仕様を共有する場合には、ドキュメントが必要になることがあります。tRPCが特に向いているのは、同じTypeScriptコードベース内でフロントエンドとバックエンドを密接に開発するケースです。社内向けアプリ、SaaS管理画面、Next.jsアプリなどでは、仕様書管理の負担を減らしながら高速に開発しやすくなります。
2. TypeScript tRPCによるフルスタック開発
TypeScript tRPCは、フルスタックTypeScript開発と非常に相性が良い技術です。フロントエンド、バックエンド、共通型、バリデーション、データ取得をTypeScriptで統一できるため、言語や型定義の分断を減らせます。従来は、フロントエンドでTypeScript、バックエンドで別言語、API仕様は別管理という構成も多くありましたが、tRPCを使うことで、アプリケーション全体をTypeScript中心に設計しやすくなります。
特に、Next.jsと組み合わせる構成では、画面、サーバー処理、API呼び出しを近い距離で管理できます。ユーザー向けページ、管理画面、認証処理、データベースアクセスを同じプロジェクトまたはモノレポ内で扱えるため、小規模なMVPから中規模のSaaSまで効率的に開発できます。ただし、大規模組織や外部公開APIを前提にする場合は、tRPCだけでなくRESTやGraphQLとの使い分けも検討する必要があります。
2.1 Next.jsとの連携
Next.jsとtRPCを連携させることで、画面側から型安全にサーバー処理を呼び出せる構成を作れます。Next.jsはフルスタックWebアプリケーションを構築できるReactフレームワークであり、サーバー側処理やルーティング、データ取得と組み合わせやすい点が特徴です。tRPCを導入すると、Next.jsアプリ内でAPIルーターを定義し、クライアント側から型安全に呼び出すことができます。
この構成では、画面開発者がAPIの戻り値を推測しながら実装する必要が少なくなります。たとえば、管理画面でユーザー一覧を取得する場合、tRPCのクライアントを通じて呼び出せば、戻り値の型が自動的に補完されます。さらに、入力値もZodなどで検証すれば、フォームから送信される値の不正をサーバー側で安全に扱えます。Next.jsとtRPCの組み合わせは、フロントエンドとバックエンドを同じチームで開発するプロジェクトに向いています。
2.2 TypeScript単一言語で開発
tRPCを活用すると、フロントエンドからバックエンドまでTypeScript単一言語で開発しやすくなります。これにより、開発者は同じ型システム、同じエディタ補完、同じ静的解析の恩恵を受けながら、画面とサーバー処理の両方を実装できます。言語の切り替えが減るため、学習コストや認知負荷を抑えられる点もメリットです。
単一言語で開発することは、チーム体制にも影響します。フロントエンド担当とバックエンド担当が完全に分断されるのではなく、型やルーター設計を共有しながら機能単位で開発しやすくなります。もちろん、データベース設計、セキュリティ、インフラ、パフォーマンスなどの専門知識は別途必要ですが、アプリケーション層の実装をTypeScriptで統一できることは、開発速度と保守性の両面で大きな利点です。
3. TypeScript tRPCでCRUD機能を実装する方法
CRUDとは、作成、読み取り、更新、削除の基本操作を指します。Webアプリケーションや管理画面では、ユーザー管理、商品管理、記事管理、注文管理など、多くの機能がCRUDを中心に構成されます。tRPCを使うと、これらの操作を型安全なプロシージャとして定義でき、フロントエンド側から安全に呼び出せます。
CRUD設計で重要なのは、各処理の責務を明確に分けることです。作成処理では入力値検証、読み取り処理では検索条件やページネーション、更新処理では変更可能な項目の制限、削除処理では権限確認や論理削除の方針が重要になります。tRPCは型安全な呼び出しを実現しますが、業務ルールやセキュリティ設計は別途きちんと実装する必要があります。
3.1 作成処理
作成処理では、新しいデータを登録するための入力値を受け取ります。たとえば、ユーザー作成、商品登録、記事投稿などが該当します。tRPCでは、作成用プロシージャに入力スキーマを設定し、必要な項目が揃っているか、形式が正しいかを検証したうえで処理を実行できます。
作成処理で注意すべきなのは、フロントエンドから送られてきた値をそのまま信用しないことです。TypeScriptの型があるからといって、実行時の値が必ず正しいとは限りません。そのため、Zodなどを使ってサーバー側で入力値を検証することが重要です。また、作成者の権限確認、重複チェック、初期値設定、監査ログの記録なども、実務では重要な設計ポイントになります。
3.2 読み取り処理
読み取り処理では、データの取得や検索を行います。ユーザー一覧、商品詳細、記事一覧、注文履歴などが代表的な例です。tRPCでは、取得処理の戻り値の型がフロントエンドへ伝わるため、画面側では取得結果を安全に扱えます。特に一覧画面では、ページネーション、検索条件、並び順、フィルター条件を型として整理できる点が便利です。
読み取り処理では、パフォーマンスにも注意が必要です。不要な項目まで返すと通信量が増え、画面描画も重くなります。Prismaなどのデータベース操作と組み合わせる場合は、必要な項目だけを取得する設計が重要です。また、認可が必要なデータでは、ログインユーザーが閲覧できる範囲だけを返すようにしなければなりません。型安全であることと、アクセス制御が安全であることは別問題であるため、両方を意識する必要があります。
3.3 更新処理
更新処理では、既存データの一部または全体を変更します。ユーザー情報の編集、商品価格の変更、記事内容の更新、ステータス変更などが該当します。tRPCでは、更新対象のIDと更新内容を型付きで受け取り、入力検証を行ったうえで処理できます。
更新処理では、変更可能な項目を明確に制限することが重要です。たとえば、ユーザーが自分の表示名を変更できても、権限やアカウント状態を自由に変更できてはいけません。入力スキーマで許可する項目を限定し、サーバー側で権限確認を行う必要があります。また、更新前後の差分を記録したり、同時編集による競合を防いだりする設計も、業務アプリでは重要になります。
3.4 削除処理
削除処理では、データを削除または無効化します。ユーザー削除、商品削除、記事削除などが代表例です。tRPCを使う場合でも、削除処理は慎重に設計する必要があります。誤削除を防ぐために、権限確認、確認画面、論理削除、復元機能、監査ログなどを検討することが重要です。
実務では、物理削除ではなく論理削除を採用することが多くあります。論理削除では、データ自体は残しつつ、削除済みフラグや削除日時を設定します。これにより、誤削除からの復元や履歴確認が可能になります。tRPCの削除プロシージャでは、削除対象のIDだけでなく、実行ユーザーの権限や対象データの状態を確認してから処理する設計が必要です。
4. TypeScript tRPCによる認証機能実装
tRPCを使ったアプリケーションでは、認証と認可の設計が非常に重要です。認証は「誰であるか」を確認する仕組みであり、認可は「何をしてよいか」を判断する仕組みです。tRPCはAPI呼び出しを型安全にできますが、認証情報の取得、セッション管理、権限チェックはアプリケーション側で設計する必要があります。
認証機能を実装する場合、tRPCのコンテキストにログインユーザー情報を含める構成がよく使われます。各プロシージャでは、そのコンテキストを参照して、ログイン済みかどうか、必要な権限を持っているかを確認します。公開API、ログイン必須API、管理者専用APIを明確に分けることで、安全で保守しやすい設計になります。
4.1 ログイン機能
ログイン機能では、メールアドレスとパスワード、外部認証、ワンタイムコードなどを使ってユーザーを認証します。tRPCでログイン処理を扱う場合、入力値を検証し、認証に成功した場合にセッションやトークンを発行します。ログイン処理はセキュリティ上重要なため、パスワードの保存方法、認証失敗時のエラーメッセージ、試行回数制限、二要素認証なども検討する必要があります。
tRPCでは、ログイン処理の戻り値も型安全に扱えます。たとえば、ログイン成功時にはユーザー情報を返し、失敗時には適切なエラーを返す設計にできます。ただし、認証失敗の理由を詳細に返しすぎると、攻撃者に情報を与える可能性があります。ユーザーに分かりやすいエラー表示と、セキュリティ上安全な情報制御のバランスが重要です。
4.2 ユーザー認可制御
認可制御では、ログイン済みユーザーがどの操作を実行できるかを判断します。たとえば、一般ユーザーは自分のプロフィールだけ編集でき、管理者は全ユーザーを管理できる、といったルールです。tRPCでは、プロシージャごとに認可チェックを組み込むことで、安全なAPIを構築できます。
認可制御を実装する際は、画面側の表示制御だけに頼ってはいけません。フロントエンドでボタンを非表示にしていても、APIを直接呼び出される可能性があります。そのため、必ずサーバー側のtRPCプロシージャで権限確認を行う必要があります。権限ロジックは共通化し、各プロシージャで同じ基準を使えるようにすると、保守性が高まります。
5. TypeScript tRPCとPrisma連携
tRPCとPrismaを組み合わせることで、型安全なAPI層と型安全なデータベースアクセス層を構築できます。PrismaはTypeScriptと相性の良いORMであり、スキーマから型付きのクライアントを生成できます。tRPC側では、Prisma Clientを使ってデータを取得・作成・更新・削除し、その結果を型安全にフロントエンドへ返すことができます。
この構成の強みは、データベースから画面まで型の流れを作りやすいことです。Prismaの型、tRPCのプロシージャ型、フロントエンドの呼び出し型が連携することで、データ構造の変更に気づきやすくなります。ただし、Prismaのモデル型をそのまま外部に返すのではなく、必要に応じてAPI用のデータ形式へ変換することが重要です。
5.1 Prisma Client活用
Prisma Clientは、データベース操作を型安全に行うためのクライアントです。tRPCのプロシージャ内でPrisma Clientを利用すると、データ取得や更新の時点で型補完が効きます。たとえば、ユーザー一覧取得、商品検索、注文作成などを実装する際に、存在しないフィールドを指定すると開発段階で気づきやすくなります。
Prisma ClientをtRPCで使う場合は、コンテキストにPrisma Clientを含める設計がよく使われます。これにより、各プロシージャから共通のデータベース接続を利用できます。また、テスト時にはモックやテスト用データベースに差し替えやすくなります。データベース操作を直接各所に散らばらせるのではなく、プロシージャやサービス層で整理すると保守しやすくなります。
5.2 型安全なデータ取得
PrismaとtRPCを組み合わせると、データ取得からフロントエンド表示まで型安全に扱いやすくなります。たとえば、Prismaで取得したユーザー情報をtRPCで返すと、フロントエンド側ではその戻り値の型を利用できます。画面側で存在しないプロパティを参照すると型エラーになるため、実装ミスを減らせます。
ただし、型安全なデータ取得を実現するには、取得するデータの範囲を適切に制御する必要があります。不要なリレーションを含めすぎると、レスポンスが大きくなり、パフォーマンスに影響します。また、パスワードハッシュや内部管理用フィールドなど、フロントエンドへ返してはいけない情報を含めないように注意が必要です。型安全性とセキュリティは両方を意識して設計する必要があります。
6. TypeScript tRPCとZod活用
tRPCとZodを組み合わせることで、入力値の型と実行時バリデーションを一体化できます。TypeScriptの型は開発時の安全性を高めますが、実行時に外部から送られてくるデータが本当に正しい形式であることまでは保証できません。Zodを使うと、文字列、数値、オブジェクト、配列などのスキーマを定義し、実行時に検証できます。
tRPCでは、プロシージャの入力にZodスキーマを設定することで、APIに渡される値を安全にチェックできます。たとえば、ユーザー作成処理では、名前が必須であること、メールアドレス形式であること、パスワードが一定文字数以上であることなどを検証できます。これにより、フロントエンドから不正な値が送られても、サーバー側で安全に拒否できます。
6.1 入力バリデーション
入力バリデーションは、tRPCアプリケーションの安全性を高めるために欠かせません。フォーム入力、検索条件、ID、ページ番号、更新内容など、外部から受け取る値は必ず検証する必要があります。TypeScript上では正しい型に見えても、実際のリクエストでは不正な値が送られる可能性があるためです。
Zodを使えば、入力値の形式を明確に定義できます。文字列の長さ、数値の範囲、メールアドレス形式、任意項目、配列の要素型などをスキーマとして表現できます。tRPCのプロシージャにZodスキーマを組み込むことで、処理本体に入る前に入力値を検証でき、ビジネスロジックを安全に保ちやすくなります。
6.2 スキーマ共有
Zodスキーマは、入力検証だけでなく型推論にも利用できます。スキーマからTypeScript型を生成できるため、バリデーションルールと型定義を別々に管理する必要が少なくなります。これにより、型では必須なのにバリデーションでは任意扱いになっている、といったズレを減らせます。
ただし、スキーマ共有も設計が重要です。フロントエンドとバックエンドで同じスキーマを使う場合、画面用の入力制約とサーバー側の厳密な検証を分ける必要があることもあります。たとえば、画面では入力途中の状態を許容する必要がありますが、サーバーでは完成した値だけを受け付けるべきです。共有するスキーマと、用途ごとに分けるスキーマを適切に判断することが重要です。
7. TypeScript tRPCで管理画面を構築する方法
tRPCは管理画面開発と相性が良い技術です。管理画面では、ユーザー管理、商品管理、注文管理、権限管理、レポート表示、ステータス変更など、多くのデータ操作が必要になります。tRPCを使うと、これらの操作を型安全なAPIとして整理でき、画面側では補完を利用しながら実装できます。
管理画面では、単にデータを表示するだけでなく、権限管理や監査ログも重要になります。管理者だけが実行できる操作、特定ロールだけが閲覧できる情報、削除や承認などの重要操作を明確に制御する必要があります。tRPCのプロシージャ設計では、公開操作、ログイン必須操作、管理者専用操作を分けることで、安全で保守しやすい管理画面を作れます。
7.1 ダッシュボード開発
ダッシュボードでは、売上、ユーザー数、注文数、問い合わせ数、アクティブ率など、複数の指標をまとめて表示します。tRPCを使うと、各指標取得用のプロシージャを型安全に定義でき、フロントエンド側では戻り値の型を確認しながらグラフやカードに表示できます。数値や期間、集計条件の型を明確にすることで、集計ロジックの誤りも減らしやすくなります。
ダッシュボード開発では、パフォーマンスにも注意が必要です。複数の重い集計処理を画面表示のたびに実行すると、レスポンスが遅くなります。必要に応じてキャッシュ、集計テーブル、非同期処理、期間指定、ページ単位の遅延読み込みを検討しましょう。tRPCは型安全な通信を提供しますが、データ取得の設計やクエリ最適化は別途必要です。
7.2 データ管理機能
管理画面の中心となるのがデータ管理機能です。ユーザー一覧、商品一覧、記事一覧、注文一覧などを表示し、検索、フィルター、並び替え、編集、削除を行います。tRPCでは、検索条件や更新内容を型付きで扱えるため、画面とAPIの連携が分かりやすくなります。
データ管理機能では、一覧取得と詳細取得を分ける設計が重要です。一覧画面では軽量なデータだけを返し、詳細画面では必要な関連情報を取得する方が効率的です。また、更新や削除では権限確認と監査ログを必ず考慮すべきです。管理画面は強い権限を持つため、不正操作や誤操作を防ぐ設計が不可欠です。
8. TypeScript tRPCによるリアルタイム通信
tRPCでは、サブスクリプションを利用してリアルタイム通信を扱うことができます。リアルタイム通信は、チャット、通知、ライブ更新、ダッシュボードの即時反映、共同編集、進捗表示などで利用されます。通常のリクエスト・レスポンス型APIでは、クライアントが定期的に取得しに行く必要がありますが、サブスクリプションを使うとサーバー側から更新を届ける設計が可能になります。
リアルタイム通信を導入する場合は、必要な場面を慎重に選ぶことが重要です。すべてのデータをリアルタイム化すると、接続管理や負荷が増えます。リアルタイムでなければ価値が下がる情報、たとえばチャットメッセージ、重要通知、進行状況、監視ダッシュボードなどに絞って導入すると効果的です。
8.1 サブスクリプション活用
サブスクリプションは、クライアントとサーバーの間で継続的な接続を持ち、サーバー側のイベントをクライアントへ届ける仕組みです。tRPCでは、通常のクエリやミューテーションに加えて、リアルタイム更新用のプロシージャを設計できます。これにより、型安全なイベントデータをクライアントへ送ることが可能になります。
サブスクリプションを使う場合は、接続のライフサイクル管理が重要です。接続が切れた場合の再接続、不要になった購読の解除、認証切れ、複数タブでの接続数増加などを考慮する必要があります。また、ユーザーごとに受け取ってよいイベントを制限する認可設計も欠かせません。
8.2 ライブ更新機能
ライブ更新機能は、ユーザーが画面を再読み込みしなくても最新情報を確認できる体験を提供します。たとえば、管理画面で新しい注文が入った瞬間に一覧を更新したり、チャット画面で新着メッセージを即時表示したりできます。tRPCのサブスクリプションを使えば、更新イベントの型も安全に扱えます。
ただし、ライブ更新はユーザー体験を向上させる一方で、実装と運用の複雑さも増します。全件を毎回再取得するのか、差分だけを反映するのか、キャッシュをどう更新するのかを設計する必要があります。リアクトクエリと組み合わせる場合は、サブスクリプションで受け取ったイベントをもとにキャッシュを更新する設計が有効です。
9. TypeScript tRPCとリアクトクエリ連携
tRPCは、フロントエンド側のデータ取得管理にリアクトクエリの考え方を活用できます。リアクトクエリは、サーバー状態の取得、キャッシュ、再取得、ローディング状態、エラー状態を管理するために使われるライブラリです。tRPCと組み合わせることで、型安全なAPI呼び出しと効率的なデータキャッシュを両立できます。
Webアプリでは、同じデータを複数画面で使ったり、更新後に一覧を再取得したり、一定時間ごとにデータを更新したりする必要があります。リアクトクエリ連携を使うと、こうしたサーバーデータの状態管理を整理しやすくなります。tRPCの型安全性とリアクトクエリのキャッシュ管理を組み合わせることで、開発体験とユーザー体験の両方を改善できます。
9.1 データキャッシュ管理
データキャッシュ管理では、取得したAPIレスポンスをクライアント側に一時保存し、同じデータを何度も取得しないようにします。たとえば、ユーザー一覧や商品一覧を画面遷移のたびに毎回取得すると、通信量が増え、表示も遅くなります。キャッシュを利用すれば、必要なタイミングだけ再取得し、画面表示を高速化できます。
tRPCとリアクトクエリを連携させると、どのAPI呼び出しの結果がどのキャッシュに対応するかを型安全に扱いやすくなります。更新処理の後に関連する一覧キャッシュを無効化したり、詳細データを再取得したりする設計も行いやすくなります。ただし、キャッシュが古くなる問題もあるため、更新タイミングや無効化ルールを明確にする必要があります。
9.2 自動再取得
自動再取得は、データの鮮度を保つために重要な機能です。画面に戻ってきたとき、ネットワークが復帰したとき、一定時間が経過したときなどにデータを再取得できます。これにより、ユーザーが古い情報を見続けるリスクを減らせます。
ただし、自動再取得を多用すると、API負荷が増える可能性があります。特に、管理画面やダッシュボードで多数のクエリを同時に再取得すると、サーバーに負荷がかかります。重要なデータは短い間隔で更新し、変化の少ないデータは長めにキャッシュするなど、データの性質に応じて設計することが重要です。
10. TypeScript tRPCによるマイクロサービス連携
tRPCは、TypeScript中心のシステム内でサービス間通信を型安全に扱うためにも利用できます。ただし、tRPCはすべてのマイクロサービス連携に最適というわけではありません。TypeScript同士の内部サービス通信には向いていますが、外部公開API、多言語環境、長期的なAPI互換性が必要な場合は、RESTやGraphQL、gRPCなども検討する必要があります。
マイクロサービス連携でtRPCを使う場合は、サービス境界と責務を明確にすることが重要です。単にルーターを増やしていくだけでは、サービス間の依存が強くなりすぎる可能性があります。どのサービスがどのデータを所有するのか、どの操作を公開するのか、障害時にどう扱うのかを設計する必要があります。
10.1 APIゲートウェイ構築
tRPCを使ってAPIゲートウェイ的な役割を作る場合、フロントエンドからの呼び出しを一つの入り口に集約し、内部サービスへ処理を振り分ける構成が考えられます。これにより、フロントエンドは複数サービスの存在を意識せず、型安全なルーターを通じて必要なデータを取得できます。
ただし、APIゲートウェイに処理を集めすぎると、巨大な中間層になってしまう危険があります。認証、認可、入力検証、集約処理はゲートウェイで扱いやすい一方、各ドメインの業務ロジックまでゲートウェイに入れると責務が曖昧になります。ゲートウェイは入口として整理し、ドメインロジックは適切なサービスに置くことが重要です。
10.2 サービス間通信
TypeScriptで書かれた内部サービス同士の通信では、tRPCによる型安全性が役立つことがあります。呼び出し側が受け取る戻り値の型を把握できるため、内部APIの変更による不整合を検出しやすくなります。特に、同じモノレポ内で複数サービスを管理している場合、型共有のメリットが大きくなります。
一方で、サービス間通信では、ネットワーク障害、タイムアウト、リトライ、冪等性、認証、監視が重要になります。tRPCは型安全な呼び出しを実現しますが、分散システム特有の課題を自動的に解決するものではありません。サービス間通信にtRPCを使う場合でも、障害設計と運用監視をセットで考える必要があります。
11. TypeScript tRPCでエラーハンドリングを実装する方法
tRPCでアプリケーションを構築する場合、エラーハンドリングの設計は非常に重要です。APIでは、入力エラー、認証エラー、認可エラー、データ未存在、業務ルール違反、外部サービス障害、予期しないサーバーエラーなど、さまざまなエラーが発生します。これらを適切に分類し、フロントエンドへ分かりやすく返すことで、ユーザー体験と保守性を向上させられます。
エラーハンドリングでは、開発者向けの詳細情報とユーザー向けの表示情報を分けることが重要です。ログには詳細な原因を記録しつつ、画面には必要最小限で分かりやすいメッセージを表示します。セキュリティ上、内部エラーやデータベース情報をそのままフロントエンドへ返すべきではありません。
11.1 カスタムエラー定義
カスタムエラーを定義すると、アプリケーション固有のエラーを整理しやすくなります。たとえば、権限不足、対象データなし、入力不正、利用上限超過、契約プラン制限などを明確に分類できます。tRPCでは、プロシージャ内で適切なエラーを投げ、フロントエンド側で種類に応じた表示や処理を行えます。
カスタムエラー設計では、エラーコード、メッセージ、詳細情報、ログレベルを整理するとよいでしょう。たとえば、ユーザーに表示する文言と、開発者が調査するための詳細情報を分けます。エラーを単なる文字列で返すだけでは、画面側で分岐しにくくなるため、構造化されたエラー設計が有効です。
11.2 フロントエンドへの型安全なエラー返却
tRPCでは、API呼び出し時のエラーもフロントエンド側で扱う必要があります。入力エラーであればフォーム項目にメッセージを表示し、認証エラーであればログイン画面へ誘導し、権限不足であればアクセス不可を表示します。エラーの種類を整理しておくと、画面側の分岐も明確になります。
型安全なエラー返却を行うには、エラー形式を統一することが重要です。すべてのプロシージャで異なる形式のエラーを返すと、フロントエンド側の処理が複雑になります。共通のエラーコードやメッセージ形式を設計し、画面側で再利用できるエラーハンドリング関数を用意すると、保守しやすくなります。
12. TypeScript tRPCとモノレポ構成
tRPCはモノレポ構成と非常に相性が良い技術です。モノレポでは、フロントエンド、バックエンド、共通型、共通ユーティリティを同じリポジトリ内で管理できます。tRPCのルーター型をフロントエンド側で参照しやすくなるため、型安全なAPI開発のメリットを最大化できます。
特に、Next.jsアプリ、APIサーバー、共通パッケージを同じリポジトリで管理する場合、tRPCの導入効果は高くなります。APIルーターをバックエンド側に定義し、その型をWebアプリや管理画面で利用できます。これにより、API仕様の重複管理を避け、変更に強い構成を作りやすくなります。
12.1 API型共有
モノレポ構成では、tRPCのルーター型をフロントエンド側へ共有しやすくなります。APIの入力値や戻り値の型を別途定義するのではなく、バックエンドのルーター定義から推論できるため、型のズレを減らせます。たとえば、apps/apiに定義したルーターを、apps/webやapps/adminから型として参照する構成が考えられます。
API型共有では、依存方向に注意が必要です。フロントエンドがバックエンドの実装そのものに依存するのではなく、型定義として参照する設計が望ましいです。実装の密結合を避けながら、型安全性だけを利用することが重要です。モノレポでは距離が近い分、依存関係が雑になりやすいため、パッケージ境界を意識しましょう。
12.2 共通パッケージ管理
tRPCモノレポでは、共通パッケージの設計が重要になります。たとえば、packages/typesには共通型、packages/validatorsにはZodスキーマ、packages/api-clientにはtRPCクライアント設定、packages/uiには共通UIを配置できます。このように責務を分けることで、各アプリが必要なパッケージだけを参照できます。
共通パッケージ管理で避けたいのは、すべてを巨大なcommonパッケージに詰め込むことです。最初は便利に見えますが、時間が経つと依存関係が不明瞭になり、変更の影響範囲が広がります。共通パッケージは小さく分け、目的を明確にし、テストとドキュメントを用意することが望ましいです。
13. TypeScript tRPCによるSaaS開発
tRPCはSaaS開発でも有効です。SaaSでは、ユーザー管理、組織管理、権限管理、契約プラン、課金、管理画面、通知、監査ログなど、多くの業務機能が必要になります。TypeScriptとtRPCを使うことで、これらのAPIを型安全に設計し、フロントエンドとバックエンドの連携を効率化できます。
SaaS開発では、単純なCRUDだけでなく、マルチテナント、ロールベースアクセス制御、プラン制限、データ分離、監査ログが重要になります。tRPCは型安全なAPI開発を支援しますが、SaaS特有の業務要件やセキュリティ要件はアプリケーション設計として慎重に実装する必要があります。
13.1 マルチテナント設計
マルチテナント設計では、複数の組織や顧客が同じアプリケーションを利用しながら、それぞれのデータを分離します。tRPCのプロシージャでは、ログインユーザーが所属する組織やテナントIDをコンテキストから取得し、その範囲内のデータだけを操作するように設計します。これにより、別組織のデータへアクセスしてしまう事故を防ぎやすくなります。
マルチテナントでは、すべてのクエリや更新処理にテナント条件を適用する必要があります。1つでも条件漏れがあると、重大な情報漏洩につながる可能性があります。そのため、共通の認可関数やデータアクセス層を用意し、各プロシージャで同じルールを使えるようにすることが重要です。型安全性だけでなく、データ分離の設計がSaaS品質を左右します。
13.2 権限管理実装
SaaSでは、管理者、メンバー、閲覧者、オーナーなど、複数の権限が存在することが多いです。tRPCのプロシージャでは、ユーザーのロールや権限に応じて、実行できる操作を制御する必要があります。たとえば、請求情報の変更はオーナーだけ、メンバー招待は管理者以上、データ閲覧はメンバー以上といったルールです。
権限管理は、画面側とサーバー側の両方で行う必要があります。画面側では不要なボタンを非表示にできますが、それだけでは不十分です。APIを直接呼び出される可能性があるため、tRPCプロシージャ内で必ず権限確認を行いましょう。権限ルールは散らばらせず、共通関数やミドルウェアとして整理すると、保守性が高まります。
14. TypeScript tRPCとサーバーアクションの使い分け
Next.jsを使う場合、tRPCとサーバーアクションの使い分けが重要になります。サーバーアクションは、サーバー側で実行される関数をフォーム送信やデータ変更に利用できる仕組みです。一方、tRPCは型安全なAPIルーターを構築し、クライアント側からクエリやミューテーションとして呼び出す設計に向いています。
どちらか一方だけを使う必要はありません。フォーム中心のシンプルな更新処理にはサーバーアクション、複数画面や複数クライアントから再利用するAPIにはtRPC、というように分けると設計しやすくなります。プロジェクトの規模、チーム構成、APIの再利用性、キャッシュ管理、認証設計に応じて選択しましょう。
14.1 tRPCが向いているケース
tRPCが向いているのは、フロントエンドから型安全にAPIを呼び出したい場合、複数画面で同じAPIを再利用したい場合、管理画面やSaaSのように多数のデータ操作がある場合です。クエリ、ミューテーション、サブスクリプションを整理し、ルーター単位でAPIを管理できるため、中規模以上のアプリケーションに向いています。
また、リアクトクエリと組み合わせてキャッシュ管理や再取得を行いたい場合もtRPCが便利です。一覧取得、詳細取得、更新後のキャッシュ無効化などを型安全に扱えます。API層を明確に持ちたいプロジェクトでは、tRPCを採用することで設計が整理されやすくなります。
14.2 サーバーアクションが向いているケース
サーバーアクションが向いているのは、フォーム送信やページに密接した単純なデータ変更です。たとえば、問い合わせフォームの送信、プロフィール更新、簡単な作成処理など、特定の画面内で完結する処理にはサーバーアクションが使いやすい場合があります。APIクライアントを別途用意せず、サーバー側の処理として自然に書ける点が特徴です。
一方で、複数画面から再利用したい処理、クライアント側のキャッシュ管理と強く連携したい処理、リアルタイム更新や複雑なAPIルーター設計が必要な場合は、tRPCの方が整理しやすいことがあります。サーバーアクションとtRPCは競合するだけでなく、用途に応じて併用できる選択肢として考えるとよいでしょう。
15. TypeScript tRPC導入のベストプラクティス
TypeScript tRPCを導入する際は、ルーター設計、型の単一ソース化、バリデーション共通化、スケーラブルなAPI構成を意識することが重要です。tRPCは簡単にAPIを追加できるため、設計ルールがないとルーターが肥大化し、どこに何の処理があるのか分かりにくくなります。最初からドメイン単位や機能単位で整理する方が、長期的に保守しやすくなります。
また、tRPCは型安全性を提供しますが、セキュリティや業務設計を自動で解決するものではありません。認証、認可、入力検証、エラーハンドリング、ログ、監査、パフォーマンス、テストを含めて設計する必要があります。型安全なAPIであることは強力な土台ですが、実務品質のアプリケーションには総合的な設計が必要です。
15.1 ルーター設計を統一する
ルーター設計では、機能やドメインごとに分割することが重要です。たとえば、ユーザー関連はユーザールーター、注文関連は注文ルーター、認証関連は認証ルーター、管理画面専用処理は管理ルーターに分けると、構造が分かりやすくなります。すべてのプロシージャを1つの巨大なルーターに入れると、規模が大きくなるほど保守が難しくなります。
ルーター名、プロシージャ名、入力スキーマ、戻り値の方針も統一しましょう。取得処理、作成処理、更新処理、削除処理の命名ルールを決めておくと、チーム内で理解しやすくなります。また、公開プロシージャ、ログイン必須プロシージャ、管理者専用プロシージャを分けることで、認可設計も明確になります。
15.2 型を単一ソース化する
tRPC導入では、型の単一ソース化が重要です。同じ意味の型をフロントエンドとバックエンドで別々に定義すると、tRPCのメリットが薄れます。バックエンドのルーター定義やZodスキーマから型を推論し、フロントエンドで再利用することで、型のズレを減らせます。
ただし、単一ソース化はすべてを共有するという意味ではありません。データベース内部の型、APIレスポンス型、画面表示用の型は分ける必要があります。共有すべきなのは、API契約として意味のある型です。内部実装を過度にフロントエンドへ漏らさず、必要な型だけを共有する設計が重要です。
15.3 バリデーションを共通化する
入力バリデーションは、tRPCアプリケーションの品質を支える重要な要素です。Zodなどを使って入力スキーマを定義し、各プロシージャで一貫した検証を行うことで、不正な値の混入を防ぎやすくなります。フォーム入力、検索条件、ページネーション、ID、更新内容などは、必ずサーバー側で検証しましょう。
バリデーションを共通化する場合は、共通スキーマを作るだけでなく、用途ごとの差分も管理する必要があります。作成時に必須の項目、更新時に任意の項目、検索時に許可する条件は異なります。共通部分を再利用しつつ、処理ごとのスキーマを明確に分けることで、安全で柔軟な設計になります。
15.4 スケーラブルなAPI構成を採用する
tRPCを長期的に使うなら、最初からスケーラブルなAPI構成を意識することが重要です。小規模な段階では単純な構成でも問題ありませんが、機能が増えるとルーター、認可、バリデーション、エラー処理、ログ、テストが複雑になります。ドメイン単位でルーターを分け、共通処理をミドルウェア化し、テストしやすい構造にしておくと拡張しやすくなります。
また、将来的に外部公開APIが必要になる可能性がある場合は、tRPCだけでなくRESTやGraphQLとの併用も考慮しましょう。tRPCはTypeScript同士の内部開発に強い一方、他言語クライアントや外部パートナー向けAPIには別の方式が適する場合があります。プロジェクトの成長に合わせて、API設計を柔軟に拡張できる構成を選ぶことが重要です。
おわりに
TypeScript tRPCは、フロントエンドとバックエンドを型安全につなぐための強力な選択肢です。バックエンドで定義したルーターやプロシージャの型をフロントエンドで利用できるため、API仕様のズレや型定義の重複を減らし、開発体験を大きく改善できます。特に、Next.js、Prisma、Zod、リアクトクエリ、モノレポ構成と組み合わせることで、フルスタックTypeScript開発の効率を高められます。
一方で、tRPCは型安全な通信を提供するものであり、認証、認可、入力検証、エラーハンドリング、監査ログ、パフォーマンス設計を自動で解決するものではありません。実務で使う場合は、ルーター設計を統一し、型を単一ソース化し、Zodなどでバリデーションを行い、権限チェックをサーバー側で徹底することが重要です。
tRPCが特に向いているのは、フロントエンドとバックエンドをTypeScriptで統一し、同じチームまたは同じリポジトリ内で開発するプロジェクトです。SaaS、管理画面、社内ツール、MVP開発、フルスタックTypeScriptアプリでは大きな効果を発揮します。プロジェクトの規模や将来の拡張性を見据えながら、tRPC、サーバーアクション、REST、GraphQLを適切に使い分けることで、保守性と開発速度を両立したAPI基盤を構築できるでしょう。
EN
JP
KR