trpc の使い方

TypeScript-first toolkit for building RPC APIs

v0.11.3/週MITWeb API
AI生成コンテンツ

この記事はAIによって生成されました。内容の正確性は保証されません。最新の情報は公式ドキュメントをご確認ください。

tRPC の使い方 — TypeScriptファーストなRPC APIツールキット

一言でいうと

tRPCは、TypeScriptの型システムを最大限に活用し、サーバーとクライアント間でエンドツーエンドの型安全性を実現するRPCフレームワークです。APIスキーマ定義やコード生成なしに、サーバーの関数をクライアントから型付きで呼び出せます。

⚠️ 重要な注意: この記事で解説する trpc パッケージ(v0.11.3)は、現在広く使われている @trpc/server / @trpc/client(v10/v11系)とは別のパッケージです。現在のtRPCエコシステムは @trpc/* スコープに移行しており、この trpc パッケージは初期の実験的バージョンです。新規プロジェクトでは @trpc/server の利用を推奨します。

どんな時に使う?

  1. フルスタックTypeScriptアプリケーション — Next.jsやモノレポ構成で、サーバーとクライアントのコードを同一リポジトリで管理し、APIの型をシームレスに共有したい場合
  2. REST/GraphQLのボイラープレートを排除したい — スキーマ定義ファイルやコード生成ステップなしに、サーバーの関数をそのままクライアントから呼び出したい場合
  3. サーバー内部でのAPI再利用 — マイクロサービス間やサーバーサイドのロジックで、自分のAPIを型安全に呼び出すServer SDKが必要な場合

インストール

# npm
npm install trpc

# yarn
yarn add trpc

# pnpm
pnpm add trpc

動作要件:

  • TypeScript 3.2以上
  • Proxy API対応環境(IE11非対応)

基本的な使い方

最もシンプルなパターンとして、エンドポイント定義 → ルーター構築 → リクエスト処理の流れを示します。

import { trpc } from 'trpc';

// 1. エンドポイントを定義
const greet = trpc.endpoint(() => (name: string) => {
  return `Hello, ${name}!`;
});

// 2. ルーターを構築
const rootRouter = trpc.router().endpoint('greet', greet);

// 3. リクエストを処理
const result = await rootRouter.handle({
  endpoint: ['greet'],
  context: {},
  args: ['World'],
});
// => "Hello, World!"

よく使うAPI

1. trpc.endpoint() — エンドポイントの定義

「関数を返す関数」を受け取ります。外側の関数がコンテキスト(認証情報など)を受け取り、内側の関数が実際のビジネスロジックです。

type Ctx = { token: string };

// コンテキストなしのシンプルなエンドポイント
const computeLength = trpc.endpoint(() => (data: string) => {
  return data.length;
});

// コンテキストありのエンドポイント
const getProfile = trpc.endpoint((ctx: Ctx) => async () => {
  const user = await getUserFromToken(ctx.token);
  return { name: user.name, email: user.email };
});

// 引数とコンテキスト両方を使うエンドポイント
const postTweet = trpc.endpoint((ctx: Ctx) => async (data: { content: string }) => {
  const user = await getUserFromToken(ctx.token);
  const newTweet = await createTweet({
    content: data.content,
    userId: user.id,
  });
  return newTweet;
});

2. .authorize() — 認可ロジックの追加

デフォルトではすべてのリクエストが拒否されます。 .authorize() で明示的に許可条件を定義する必要があります。

const getUsername = trpc
  .endpoint((ctx: Ctx) => async () => {
    const user = await getUserFromToken(ctx.token);
    return user.username;
  })
  .authorize((ctx) => async (_data) => {
    if (!ctx.token) return false;
    return true;
  });

3. trpc.router() / .endpoint() / .compose() — ルーターの構築と合成

ルーターはイミュータブルです。必ずメソッドチェーンで構築してください。

// エンドポイントの登録(チェーン必須)
const userRouter = trpc
  .router()
  .endpoint('getUsername', getUsername)
  .endpoint('postTweet', postTweet);

// ルーターの階層化
const rootRouter = trpc
  .router()
  .endpoint('computeLength', computeLength)
  .compose('user', userRouter);

// ⛔ これは動かない!ルーターはイミュータブル
// const router = trpc.router();
// router.endpoint('a', endpointA);
// router.endpoint('b', endpointB);

4. router.toServerSDK() — サーバーサイドSDKの生成

サーバー内部でAPIを型安全に呼び出すためのSDKを自動生成します。認可チェックを自動的にバイパスするため、クライアントに露出させないよう注意が必要です。

const serverSDK = rootRouter.toServerSDK();

// 第1引数は常にコンテキスト
const length = serverSDK.computeLength({}, 'hello'); // => 5

// ネストしたルーターのエンドポイントも呼べる
const username = await serverSDK.user.getUsername({ token: 'SAMPLE_TOKEN' });

const newTweet = await serverSDK.user.postTweet(
  { token: 'SAMPLE_TOKEN' },
  { content: 'just setting up my twttr' },
);

5. trpc.sdk() — クライアントSDKの生成

サーバーのルーター型を型のみインポートし、クライアントSDKを生成します。

// client.ts
import { trpc } from 'trpc';

// 型だけをインポート(実際のサーバーコードはバンドルされない)
type RootRouter = typeof import('./server').rootRouter;

const clientSDK = trpc.sdk<RootRouter>({
  url: 'http://localhost:3000/rpc',
  getContext: () => {
    const token = getTokenFromCookies();
    return { token };
  },
});

// SDK呼び出しはTRPCResult<T>を同期的に返す
const query = clientSDK.computeLength('asdf');

// .run()で実際にサーバーへリクエストを送信
const result = await query.run();
// => 4

TRPCResult<T> の構造:

type TRPCResult<T> = {
  run: () => Promise<T>;
  payload: {
    path: string[];
    args: unknown[];
    context: unknown;
  };
};

Express連携

import express from 'express';
import bodyParser from 'body-parser';

const app = express();
app.use(bodyParser.json());

// 単一エンドポイントでAPI全体を公開(GraphQLと同様のパターン)
app.post('/rpc', rootRouter.toExpress());

app.listen(3000);

類似パッケージとの比較

特徴trpc (v0.11)@trpc/server (v10/v11)GraphQL (Apollo等)REST (Express等)
型安全性✅ エンドツーエンド✅ エンドツーエンド⚠️ コード生成が必要❌ 手動定義
スキーマ定義不要不要必要不要(OpenAPI等は別途)
コード生成不要不要必要必要(型安全にする場合)
エコシステム⚠️ 実験的・非推奨✅ 活発・React/Next.js統合✅ 成熟✅ 成熟
学習コスト
バリデーションなしZod等と統合スキーマで定義手動
サブスクリプション❌(WebSocket別途)

結論: 新規プロジェクトでtRPCのアプローチを採用するなら、@trpc/server + @trpc/client を使うべきです。

注意点・Tips

🚨 ルーターはイミュータブル

最もハマりやすいポイントです。.endpoint() は新しいルーターを返すため、必ずチェーンするか戻り値を変数に代入してください。

// ✅ 正しい
const router = trpc.router()
  .endpoint('a', endpointA)
  .endpoint('b', endpointB);

// ❌ 間違い(endpointBしか登録されない、ではなく、そもそも型が合わない)
const router = trpc.router();
router.endpoint('a', endpointA);
router.endpoint('b', endpointB);

🚨 デフォルトで全リクエスト拒否

.authorize() を付けないエンドポイントはリクエストが拒否されます。開発中に「なぜ動かないのか」と悩んだら、まず認可設定を確認してください。

🚨 Server SDKは認可をバイパスする

toServerSDK() で生成したSDKは認可チェックをスキップします。クライアントバンドルに含まれないよう、インポートパスに細心の注意を払ってください。

💡 クライアントSDKでは型のみインポートする

// ✅ 型だけインポート(サーバーコードはバンドルされない)
type RootRouter = typeof import('./server').rootRouter;

// ❌ 実体をインポートしてしまう(サーバーコードがクライアントに漏洩)
import { rootRouter } from './server';

💡 TRPCResult の遅延実行パターン

クライアントSDKの呼び出しは即座にリクエストを送信しません。.run() を呼ぶまでリクエストは遅延されます。これにより、ペイロードの検査やバッチ処理が可能になります。

const query = clientSDK.computeLength('test');
console.log(query.payload); // リクエスト内容を事前に確認できる
const result = await query.run(); // ここで初めてHTTPリクエストが発生

まとめ

trpc(v0.11)は、TypeScriptの型推論を活用してサーバー・クライアント間のAPI型安全性を実現する先駆的なパッケージです。エンドポイント定義・ルーター構築・SDK自動生成というシンプルな3ステップで、スキーマ定義やコード生成なしに型付きAPIを構築できます。

ただし、このパッケージは実験的な初期バージョンであり、現在は @trpc/server / @trpc/client(v10/v11系)へと進化しています。新規プロジェクトでは @trpc/* スコープのパッケージを採用し、Zodによるバリデーション統合やReact Query連携など、成熟したエコシステムの恩恵を受けることを強く推奨します。