TRPC 101の実装
tRPC(TypeScript Remote Procedure Call)はフロントエンドとバックエンドのギャップを排除します。サーバー関数をクライアントから直接呼び出すことができます。完全なTypeScript推論、コード生成なし、管理するAPIスキーマなしです。
tRPCとは?
tRPCはTypeScriptで型安全なエンドツーエンドAPIを構築するためのフレームワークです。RESTルートやGraphQLスキーマを定義する代わりに、サーバーに通常のTypeScript関数を記述し、クライアントからローカル関数のように呼び出します。バックエンドの戻り値の型を変更すると、フロントエンドは即座に型エラーを表示します。ビルドステップやコード生成は不要です。
RESTやGraphQLと比べてなぜtRPC?
| REST | GraphQL | tRPC | |
| 型安全性 | 手動 | コード生成で対応 | 組み込み |
| ボイラープレート | 多い | 多い | 最小限 |
| スキーマ定義 | OpenAPI / Swagger | SDL + リゾルバー | 不要 |
| 学習曲線 | 低い | 急である | 低い |
| 最適な用途 | パブリックAPI | 複雑なグラフデータ | フルスタックTSモノレポ |
tRPCはフロントエンドとバックエンドの両方がTypeScriptの場合に最適です。非TSクライアントによって使用されるパブリックAPIが必要な場合は、RESTまたはGraphQLが適しています。
フロントエンド&バックエンド間のブリッジ
tRPCはUIとサーバーロジックの間に位置する型安全なAPIレイヤーとして機能します。ピースの接続方法は次の通りです:
┌─────────────────────────────────────────────────┐
│ フロントエンド (React / Next.js) │
│ │
│ trpc.dashboard.getStats.useQuery() │
│ ↕ 完全な型推論、fetch()なし │
├─────────────────────────────────────────────────┤
│ tRPCクライアント → httpBatchLink → /api/trpc │
├─────────────────────────────────────────────────┤
│ tRPCルーター(APIレイヤー) │
│ │
│ ├── dashboardRouter │
│ │ ├── getStats (query) │
│ │ └── updateConfig (mutation) │
│ ├── userRouter │
│ │ ├── me (query) │
│ │ └── updateProfile(mutation) │
│ └── notificationRouter │
│ └── onNew (subscription) │
├─────────────────────────────────────────────────┤
│ ハンドラー / ビジネスロジック │
│ (DBクエリ、外部API、変換) │
└─────────────────────────────────────────────────┘
重要なポイント:AppRouter型はサーバーからエクスポートされ、クライアントにインポートされます。型だけが境界を越えます。サーバーからクライアントへランタイムコードは流出しません。
コアコンセプト
ルーターとプロシージャー
ルーターは関連するプロシージャーをグループ化します。プロシージャーは単一のエンドポイントです。query(読み取り)、mutation(書き込み)、またはsubscription(リアルタイムストリーム)のいずれかです。
コンテキスト
コンテキストオブジェクトはリクエストごとに作成され、すべてのプロシージャーに渡されます。ここでデータベース接続、認証セッション、リクエストメタデータを添付します。
ミドルウェア
ミドルウェアはプロシージャーをラップして、認証、ロギング、レート制限などのクロスカッティングの関心事を処理します。ミドルウェアはコンテキストも交換できるため、下流のプロシージャーは充実したデータ(検証済みのuserオブジェクトなど)を取得します。
入力検証
tRPCはZod(または.parse()メソッドを持つ任意のバリデーター)を使用して、ランタイムで入力を検証します。検証された型は自動的にクライアントに流れます。
セットアップガイド(Next.js App Router + tRPC v11)
1. 依存関係をインストール
npm install @trpc/server @trpc/client @trpc/tanstack-react-query \
@tanstack/react-query@latest zod client-only server-only superjson
superjsonはオプションですが推奨されます。これにより、Date、Map、Set、およびその他の非JSONタイプをクライアントとサーバー間でシームレスに送信できます。
2. tRPCを初期化(サーバー)
/trpc/init.tsを作成:
import { initTRPC, TRPCError } from '@trpc/server';
import { cache } from 'react';
import superjson from 'superjson';
// コンテキストはリクエストごとに一度作成されます
export const createTRPCContext = cache(async () => {
// 認証セッション、DB接続など
return {
userId: null as string | null,
};
});
const t = initTRPC.context<typeof createTRPCContext>().create({
transformer: superjson,
});
export const router = t.router;
export const publicProcedure = t.procedure;
export const middleware = t.middleware;
3. 最初のルーターを作成
/trpc/routers/dashboard.tsを作成:
import { z } from 'zod';
import { router, publicProcedure } from '../init';
export const dashboardRouter = router({
// クエリ — データを取得
getStats: publicProcedure.query(async () => {
// ここにDBコールまたはビジネスロジック
return {
totalUsers: 1250,
activeToday: 342,
revenue: 48000,
};
}),
// ミューテーション — データを変更
updateConfig: publicProcedure
.input(
z.object({
theme: z.enum(['light', 'dark']),
timezone: z.string(),
})
)
.mutation(async ({ input }) => {
// 設定をDBに保存
return { success: true, applied: input };
}),
});
4. アプリルーターにマージ
/trpc/router.tsを作成:
import { router } from './init';
import { dashboardRouter } from './routers/dashboard';
import { userRouter } from './routers/user';
export const appRouter = router({
dashboard: dashboardRouter,
user: userRouter,
});
// 型だけをエクスポート — ルーター自体ではありません
export type AppRouter = typeof appRouter;
5. APIハンドラーを作成
/app/api/trpc/[trpc]/route.tsを作成:
import { fetchRequestHandler } from '@trpc/server/adapters/fetch';
import { createTRPCContext } from '@/trpc/init';
import { appRouter } from '@/trpc/router';
const handler = (req: Request) =>
fetchRequestHandler({
endpoint: '/api/trpc',
req,
router: appRouter,
createContext: createTRPCContext,
});
export { handler as GET, handler as POST };
6. クライアントプロバイダーをセットアップ
/trpc/client.tsxを作成:
'use client';
import { createTRPCClient, httpBatchLink } from '@trpc/client';
import { createTRPCContext } from '@trpc/tanstack-react-query';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { useState } from 'react';
import superjson from 'superjson';
import type { AppRouter } from './router';
// 型付きフックを作成
const { TRPCProvider, useTRPC } = createTRPCContext\\<AppRouter>();
function getBaseUrl() {
if (typeof window !== 'undefined') return '';
if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`;
return `http://localhost:${process.env.PORT ?? 3000}`;
}
export function TRPCClientProvider({ children }: { children: React.ReactNode }) {
const [queryClient] = useState(() => new QueryClient());
const [trpcClient] = useState(() =>
createTRPCClient\\<AppRouter>({
links: [
httpBatchLink({
url: `${getBaseUrl()}/api/trpc`,
transformer: superjson,
}),
],
})
);
return (
<QueryClientProvider client={queryClient}>
<TRPCProvider trpcClient={trpcClient} queryClient={queryClient}>
{children}
</TRPCProvider>
</QueryClientProvider>
);
}
export { useTRPC };
\\<TRPCClientProvider>でルートレイアウトをラップします。
7. フロントエンドからプロシージャーを呼び出す
'use client';
import { useTRPC } from '@/trpc/client';
import { useQuery, useMutation } from '@tanstack/react-query';
export function DashboardStats() {
const trpc = useTRPC();
// 型安全なクエリ — 戻り値の型は自動的に推論されます
const { data, isPending } = useQuery(trpc.dashboard.getStats.queryOptions());
// 型安全なミューテーション
const updateConfig = useMutation(
trpc.dashboard.updateConfig.mutationOptions()
);
if (isPending) return <div>読み込み中...</div>;
return (
<div>
<h2>総ユーザー数: {data?.totalUsers}</h2>
<button onClick={() => updateConfig.mutate({ theme: 'dark', timezone: 'Asia/Tokyo' })}>
ダークモードに切り替え
</button>
</div>
);
}
ミドルウェアと認証
ミドルウェアはルートを保護し、コンテキストを充実させる方法です。一般的な認証パターンは次の通りです:
import { TRPCError } from '@trpc/server';
import { middleware, publicProcedure } from './init';
// 認証ミドルウェア — 有効なセッションをチェック
const isAuthed = middleware(async ({ ctx, next }) => {
if (!ctx.userId) {
throw new TRPCError({
code: 'UNAUTHORIZED',
message: 'ログインする必要があります',
});
}
// コンテキストを交換 — 下流のプロシージャーはuserIdが保証されます
return next({
ctx: { userId: ctx.userId },
});
});
// ロギングミドルウェア
const withLogging = middleware(async ({ path, type, next }) => {
const start = Date.now();
const result = await next();
console.log(`${type} ${path} — ${Date.now() - start}ms`);
return result;
});
// 再利用可能なプロシージャータイプを作成
export const protectedProcedure = publicProcedure.use(isAuthed);
export const loggedProcedure = publicProcedure.use(withLogging);
認証が必要なルートにはpublicProcedureの代わりにprotectedProcedureを使用します:
export const userRouter = router({
me: protectedProcedure.query(async ({ ctx }) => {
// ctx.userIdはここで文字列であることが保証されます
return await db.user.findUnique({ where: { id: ctx.userId } });
}),
});
エラーハンドリング
tRPCはHTTPステータスコードにマップされる構造化エラーコードを提供します:
import { TRPCError } from '@trpc/server';
// 任意のプロシージャー内
throw new TRPCError({
code: 'NOT_FOUND', // → 404
message: 'ユーザーが見つかりません',
});
throw new TRPCError({
code: 'BAD_REQUEST', // → 400
message: 'メールアドレスの形式が無効です',
});
throw new TRPCError({
code: 'FORBIDDEN', // → 403
message: '管理者アクセスが必要です',
});
初期化時にグローバルにエラーフォーマットをカスタマイズして、Zod検証の詳細などのメタデータを含めることもできます。
プロジェクト構造
/trpc
├── init.ts # tRPC初期化、コンテキスト、基本プロシージャー
├── router.ts # すべてのサブルーターをマージするrootアプリルーター
├── client.tsx # クライアントプロバイダー + フック
└── routers/
├── dashboard.ts # ダッシュボード関連プロシージャー
├── user.ts # ユーザーCRUDプロシージャー
└── notification.ts
/app
└── api/trpc/[trpc]
└── route.ts # APIハンドラー(fetchアダプター)
ルーターは小さく焦点を絞ったものにしてください。各ルーターはドメインエリアにマップされます。APIが成長するにつれて、整理された状態を保ちます。
tRPCをいつ使うか
次の場合にtRPCを使用します:
-
フロントエンドとバックエンドの両方がTypeScript
-
モノレポまたはフルスタックフレームワーク(Next.js、SvelteKit)
-
コード生成オーバーヘッドなしの型安全性
-
内部ダッシュボード、管理パネル、SaaSアプリを構築中
次の場合はtRPCをスキップします:
-
非TypeScriptクライアントによって使用されるパブリックAPIが必要
-
バックエンドがGo、Python、または別の非TS言語
-
Server Actionsで十分なシンプルなCRUD
-
フルスタックTSの仮定が崩れるマイクロサービス
便利なリンク
| リソース | URL |
| tRPCドキュメント | trpc.io/docs |
| tRPC v11アナウンスメント | trpc.io/blog/announcing-trpc-v11 |
| Next.js App Routerセットアップ | trpc.io/docs/client/nextjs |
| TanStack React Query | tanstack.com/query |
| Zodバリデーション | zod.dev |
| マイグレーションガイド(v10 → v11) | trpc.io/docs/migrate-from-v10-to-v11 |
最終更新: 2026年3月28日