🕌

【T3 Stack入門】tRPCの基礎 vol.1 (メリット編)

2024/08/27に公開

はじめに

tRPCは、型安全なAPIを構築するためのライブラリです。近年フロントエンドを中心に注目を集めているT3 Stackにも採用されており、TypeScriptを活用したフルスタック開発においてその威力を発揮します。

本記事では、tRPCと他のAPI構築ツール(OpenAPI、GraphQL、gRPC)との比較を通じて、T3 StackにおけるtRPCのメリットを解説します。

T3 Stackの全体像については以下の記事で解説していますので、興味があればご参照ください。
https://zenn.dev/maicom/articles/efafe3fc3f40e2

Open API / Graph QL / gRPCとの比較

APIを構築するライブラリはtRPC以外にも、Open API / Graph QL / gRPCなど色々な選択肢があります。これらの特徴を軽く押さえた上で、tRPCの優位性について検討してみましょう。

OpenAPI (Swagger)の特徴

OpenAPIは、REST APIのスキーマを標準化するために開発された仕様で、APIドキュメントやクライアントコードの自動生成が可能です。

メリット

  • 詳細なスキーマ定義が可能で、型安全なクライアントの自動生成が容易。
  • 豊富なエコシステムを持ち、開発ツールや運用ツールとの統合が容易。
  • 多言語対応が可能で、大規模プロジェクトにも適している。

デメリット

  • スキーマ定義のための作業が増え、初期設定やメンテナンスに時間がかかる。
  • REST APIの柔軟性を損なう場合があり、特に非標準的なリソース操作が難しい。
  • 学習コストがやや高く、スキーマ設計に対する理解が必要。

GraphQLの特徴

GraphQLは、Facebook(現Meta)によって開発されたクエリ言語で、APIのデータ取得をクライアント主導で行うことができます。

メリット

  • クライアントが必要なデータだけを取得でき、オーバーフェッチやアンダーフェッチを防げる。
  • 単一のエンドポイントで複数リソースへのアクセスが可能。
  • 型安全で、クエリに基づいた自動ドキュメント生成が可能。

デメリット

  • 学習コストが高く、複雑なクエリ設計や最適化が求められる。
  • サーバーサイドでのパフォーマンスチューニングが難しい場合がある。
  • 非常に柔軟だが、適切に設計しないとパフォーマンスが低下するリスクがある。

gRPCの特徴

gRPCは、Googleが開発したリモートプロシージャコール(RPC)フレームワークで、Protocol Buffers(Protobuf)を用いたバイナリシリアライゼーションを特徴とします。高速で軽量な通信が可能です。

メリット

  • 高速で効率的な通信が可能で、特にリアルタイム性が求められるシステムに強い。
  • ストリーミングや双方向通信が標準でサポートされている。
  • 型安全で、多言語対応が容易であり、スケーラビリティに優れている。

デメリット

  • 学習コストが高く、特にProtobufの理解が必要。
  • RESTと比較して導入が複雑であり、HTTP/2に対応していないクライアントには使用できない。
  • Webブラウザとの連携が難しく、通常は専用のゲートウェイが必要となる。

それぞれの型安全性、学習曲線、エコシステム、パフォーマンス、クライアントサイドへの実装(Next.js)の難易度を表にまとめると以下となります。

Open API / Graph QL / gRPC / tRPC比較表

型安全性 学習曲線 エコシステム パフォーマンス Next.jsへの実装
OpenAPI 中~高(スキーマ定義あり) 豊富 容易(ドキュメント生成に追加設定必要)
GraphQL 高(スキーマ定義あり) 豊富 中~高(オーバーフェッチ回避可能) やや複雑(Apollo ServerをAPI routeとして実装)
gRPC 高(Protocol Buffersによる強力な型定義) 中~小 非常に高(バイナリプロトコル) 困難(HTTP/2要件のため直接統合が難しい)
tRPC 非常に高(エンドツーエンドの型安全性) 小(成長中) 非常に容易(Next.jsと高い親和性)

tRPCのメリットとは

tRPCのメリットは以下の3点が挙げられます。

  1. スキーマやコード生成が不要
  2. 自動補完で開発効率UP
  3. エンドツーエンドの型安全性

では、順を追って説明します。

1. スキーマやコード生成が不要

tRPCはGraphQLやOpenAPIのようにスキーマを明示的に定義する必要がありません。サーバーサイドの型定義がそのままクライアントに反映されます。その工程が不要であることでどれくらいフローがシンプルになるのか、スキーマが必要な従来のAPI構築フローと比較してみましょう。

従来のAPI構築フロー

  1. スキーマの作成
    スキーマはAPIのデータ構造や操作の仕様を定義します。このスキーマに基づいて型が生成され、クライアントが取得するデータの形式や構造が決定されます。

  2. 型生成ツールの設定ファイルを作成
    型生成に使用するツールの設定ファイルをプロジェクトのルートに作成します。設定ファイルには、スキーマのパスや生成する型定義の出力先を指定します。

  3. 型定義を生成するコマンドを実行
    ターミナルで型定義生成コマンドを実行し、スキーマに基づいた型定義ファイルを生成します。

  4. 生成された型定義をクライアントサイドで利用
    生成された型定義を利用して、クライアントサイドでAPIリクエストを実行します。

  5. 型定義の更新
    スキーマに変更があった場合は、再度型定義生成コマンドをターミナルで実行して型定義を更新します。

tRPCのAPI構築フロー

  1. 型定義とルーターを作成
    サーバーサイドでAPIの型定義とルーターをTypeScriptで作成します。この型定義はそのままクライアント側でも利用されます。

  2. 型定義をインポートして利用
    サーバーサイドで定義された型定義をクライアントサイドにインポートし、型安全にAPIリクエストを実行します。

tRPCはかなりシンプルなフローになっていますね!これは開発スピードUPに大きく貢献してくれるでしょう。
続いて、第2のメリットであるtRPCの型情報を利用した自動補完について説明します。

2. 自動補完で開発効率UP

tRPCではクライアントとサーバー間で型情報を共有することで、VS CodeなどのIDEの型補完を利用しながら効率的にコーディングを進めることができます。

たとえば、サーバーサイドで以下のようなcreateというエンドポイントが用意されていた場合、どのように自動補完が働くのか説明します。なお、説明にはAPI実装とAPI呼び出しのコードをのみ抜粋し、APIサーバーの立ち上げなどのコードは省略しています。

// tRPCのルーターを作成しています。ルーターはAPIエンドポイントの集合を管理するものです。
export const postRouter = createTRPCRouter({
  create: publicProcedure
    // 入力のバリデーションをzodを使ってstring型を定義しています。
    .input(z.object({ name: z.string().min(1) }))
    .mutation(async ({ ctx, input }) => {
      return ctx.db.post.create({
        data: {
          name: input.name,
        },
      });
    }),

このcreate エンドポイントをクライアントサイドで呼び出す際は、戻り値の型が自動的に推論されます。補完情報がIDE上に(画像はVS Code)表示され、戻り値にname が存在することがわかりやすく、コーディングの助けになります。

この自動補完機能はOpenAPIやGraphQLの型定義生成ツールを使えば同じように可能ではありますが、前述の通りスキーマ不要で型定義ができるtRPCの方が手間を最小限にメリットを享受できます。

続いて、tRPC最大のメリットであるエンドツーエンドの型安全性について説明します。

3. エンドツーエンドの型安全性

tRPCでは、サーバー側のコードを更新すれば、クライアント側でも自動的に型チェックが行われ、誤った入力やAPI呼び出しを防ぐことができます。

「2. 自動補完で開発効率UP」に登場したcreate エンドポイントを再び例として出します。入力値と戻り値にnumber型のageプロパティを追加する変更がサーバーサイドで行われたとします。

  create: publicProcedure
  // 入力値と戻り値にnumber型の"age"を追加
    .input(z.object({ name: z.string().min(1), age: z.number().int() }))
    .mutation(async ({ ctx, input }) => {
      return ctx.db.post.create({
        data: {
          name: input.name,
          age: input.age,
        },
      });
    }),

すると、クライアントサイドでcreateを呼び出していた箇所で、age プロパティが不足していることにより型エラーが発生します。

tRPCではサーバーサイドの型定義がクライアントサイドに自動的に反映されるため、型の不一致が即座に検出されます。

GraphQLやOpenAPIのでは、更新されたスキーマに基づいて新しい型定義を生成する必要があります。このステップが完了しないと、クライアント側ではサーバーサイドの変更が反映されず、古い型定義のままになってしまいます。

tRPCでは、サーバーサイドで型定義を変更すると、その変更が即座にクライアントサイドに反映されるため、型の不一致やエラーがリアルタイムで検出されます。これにより、サーバーとクライアント間の連携が非常にスムーズになり、型安全性を保ちながら、効率的に開発を進めることができます。開発者は、手動で型定義を再生成する必要がなく、安心してコードの変更やリファクタリングを行うことが可能になります。

この違いが、tRPCの大きな利点となり、特に開発速度やメンテナンス性において、他のアプローチと比べて大きな優位性を提供します。

tRPCが向いているプロジェクト

tRPCのメリットについて説明してきましたが、そのメリットは同時にデメリットにもなり得ます。特に、TypeScriptに依存したエコシステムやクライアントとサーバーの強い結びつきが、スケーラビリティや異なる言語環境での導入の障害となる可能性があります。したがって、tRPCの採用は、プロジェクトの規模や技術スタック、将来の拡張性を考慮して慎重に判断する必要があります。

では、tRPCが特に効果を発揮するプロジェクトとはどのようなものでしょうか?

T3 Stack発案者であるTheo氏のツイートによると、tRPCが向いているプロジェクトは「バックエンドもフロントエンドも同じチームであること(同一リポジトリ)」かつ、「クライアントもサーバーもTypeScriptで書かれていること」とあります。

https://x.com/t3dotgg/status/1598100163528052736

このようなプロジェクトでは、tRPCのエンドツーエンドの型安全性や、スキーマレスな開発による迅速なAPI構築が大きなメリットとなります。また、同一リポジトリでの開発により、クライアントとサーバー間の密接な依存関係も効率的に管理でき、スケーラビリティや複雑さの課題もコントロールしやすくなります。

T3 Stackは、同一リポジトリでフルスタック開発をTypeScriptで行うため、tRPCはその開発スタイルに最適な技術と言えるでしょう。

まとめ

今回は、tRPCの特徴や他のAPI構築手法との違いについてご紹介しました。tRPCは、スキーマやコード生成が不要であること、エンドツーエンドの型安全性が保たれること、自動補完による開発効率の向上といったメリットがあります。特に、TypeScriptを用いたフルスタック開発において、その優位性が際立ちます。

他の手法と比較しても、tRPCは開発スピードを維持しつつ、型安全性を確保できる点で大きな強みを持っています。プロジェクトの規模や要件に応じて、tRPCを活用することで、より効率的で安定した開発が可能になるでしょう。

Discussion