tRPCとは?REST・GraphQL・gRPCとの違いと型安全なAPI実装をv11の実コードで解説
tRPCは、TypeScriptで書いたサーバーの関数を、クライアントから型情報ごとそのまま呼び出せるようにするRPCフレームワークです。スキーマ定義もコード生成も挟まず、サーバーの型がそのままクライアントの補完・型チェックに効くため、フロントとバックエンドの間で「送るデータの形」がずれた瞬間にビルドが失敗します。本記事では、tRPCがREST・GraphQL・gRPCとどう違うのか、なぜ型安全になるのか、最新のv11(11.18.0時点)でのサーバー/クライアント実装、そして採用すべきでない場面までを実コードで示します。
まとめ:tRPCの結論
先に要点をまとめます。
- tRPCとは:TypeScript専用のRPCフレームワーク。サーバーの関数(Procedure)の型を、クライアントが
importした型情報だけで呼び出せる。スキーマ言語もコード生成も不要。 - 型安全の仕組み:REST/GraphQL/gRPCが「スキーマ→コード生成→型」を経るのに対し、tRPCは同じリポジトリ内でサーバーの型を直接参照する。ビルド時に型がつながるので、生成物のズレが起きない。
- 向く場面:Next.jsやT3 Stackのような、フロントもバックもTypeScriptで一つのリポジトリ(モノレポ)にまとまった開発。
- 向かない場面:第三者に公開するAPI、TypeScript以外のクライアント(ネイティブアプリ・他言語)、言語がばらばらのマイクロサービス。ここではREST・GraphQL・gRPCの方が適する。
以下で、この違いと実装を順に見ていきます。
tRPCとは:型安全なAPIを実現するRPCフレームワーク
tRPCは「TypeScript Remote Procedure Call」を指し、その名の通りリモートの関数呼び出しをTypeScriptの型システムだけで型安全にする仕組みです。RESTやGraphQLのように「エンドポイントのURL」や「クエリ言語」を介さず、クライアントはサーバーが公開した関数を、あたかも同じコードベースの関数のように呼び出します。
サーバーの型をクライアントが直接参照する仕組み
tRPCの中心にあるのは、サーバー側で定義したルーターの型を、クライアントがimport typeで受け取るという一点です。実行時にサーバーが型情報を送るわけではありません。ビルド時にTypeScriptコンパイラがサーバーとクライアント両方の型を突き合わせ、引数や戻り値の形が違えばコンパイルエラーになります。APIの入力を変更したのにフロントを直し忘れれば、その場でビルドが赤くなる、というのがtRPCの体験の核です。
スキーマもコード生成も挟まない設計
OpenAPIやGraphQL SDL、Protocol Buffersの.protoのような「別言語で書くスキーマ」を、tRPCは持ちません。中間生成物がないため、スキーマ定義ファイルの更新やコード生成コマンドの実行を忘れて型がずれる、という事故が構造的に起きません。入力バリデーションはZodによる型安全なバリデーションなどのスキーマライブラリを.input()に渡して行い、その型が自動で推論に乗ります。
動作条件:TypeScriptモノレポが前提
この「型を直接共有する」設計の裏返しとして、tRPCはサーバーとクライアントが同じTypeScriptのコードベース(モノレポ)にあることを前提とします。v11はpeer dependencyとして[email protected]以上を要求し、tsconfigのstrict有効化が推奨です。型を共有できない構成(別言語のフロント、別リポジトリの外部チーム)では、tRPC最大の利点が消えます。この線引きが、後述する「採用すべきでない場面」に直結します。
tRPCとREST・GraphQL・gRPCの違い
tRPCの立ち位置は、他のAPI方式と並べると明確になります。まず全体像を表で示し、その後で方式ごとの違いを掘り下げます。
| 観点 | REST | GraphQL | gRPC | tRPC |
|---|---|---|---|---|
| スキーマ定義 | 任意(OpenAPI) | SDL(必須) | .proto(必須) | 不要 |
| コード生成 | 任意 | ほぼ必須 | 必須 | 不要 |
| 対応言語 | 言語非依存 | 言語非依存 | 多言語 | TypeScriptのみ |
| 型共有 | 生成物経由 | 生成物経由 | 生成物経由 | import(直接) |
| 通信 | HTTP/JSON | HTTP/JSON | HTTP/2・protobuf | HTTP/JSON |
| 主な用途 | 公開API全般 | 柔軟な取得 | 高速な内部通信 | TSフルスタック |
RESTとの違い:エンドポイント設計か、関数呼び出しか
RESTはリソースをURLとHTTPメソッドで表現し、レスポンスの型はクライアントが別途定義するか、OpenAPIから生成します。tRPCはURL設計そのものを開発者から隠し、関数呼び出しの形に置き換えます。REST側で型安全を得るにはOpenAPIスキーマの管理と生成が要りますが、tRPCはその工程自体が存在しません。一方でRESTは言語もクライアントも選ばず、キャッシュやHTTPの標準機構に素直に乗るため、公開APIでは依然として第一候補です。
GraphQLとの違い:スキーマ言語と学習コスト
GraphQLは単一エンドポイントにクエリ言語で「欲しいフィールドだけ」を要求でき、オーバーフェッチ/アンダーフェッチを抑えられます。その代わりSDLでのスキーマ定義、リゾルバ、クライアント側のコード生成といった仕組みを学ぶ必要があります。tRPCはクエリ言語を持たず、必要なデータは関数の戻り値としてそのまま返す素朴な設計で、学習コストは小さい反面、GraphQLのような柔軟な部分取得はできません。GraphQLとRESTの関係はGraphQLとREST APIの違いで整理しています。
gRPCとの違い:多言語・高速通信か、TS特化か
gRPCはProtocol Buffersで.protoを定義し、HTTP/2上でバイナリをやり取りする多言語対応・高性能の仕組みです。マイクロサービス間の高速通信に向く一方、ブラウザから直接呼ぶにはgRPC-webやConnectのような橋渡しが要ります。tRPCはTypeScript専用で.protoもコード生成もなく、ブラウザ・Node間の通信をそのまま扱えます。多言語や厳密なスキーマ契約が必要ならgRPC、TypeScriptに閉じるならtRPC、という住み分けです。gRPCの詳細はgRPCの仕組みと実装、ブラウザ対応の橋渡しはConnectの特徴を参照してください。
方式選択の判断軸
選び方は一点に絞れます。クライアントもサーバーもTypeScriptで同じリポジトリにあるならtRPC、そうでなければREST・GraphQL・gRPCです。tRPCは「速い・優れている」ではなく、適用条件が狭い代わりにその条件下で開発体験が突出する性格のツールで、条件に収まらないケースの具体は後述の「採用すべきでない場面」で扱います。
サーバー側がPHPで、それでも関数を呼ぶ感覚のAPIにしたい場合は、JSON-RPC 2.0が選択肢になります。Laravelでの実装手順はLaravel Sajyaとは?JSON-RPC 2.0サーバーの実装手順とエラー処理で解説しています。
tRPC v11のセットアップ:サーバーからクライアントまで
最新のv11でのサーバー/クライアント実装を示します。パッケージは@trpc/serverと@trpc/clientで、入力検証にZodを併用します。
サーバー:ルーターとProcedureの定義
initTRPC.create()でインスタンスを作り、routerとpublicProcedureを書き出します。各Procedureは.input()にZodスキーマを渡し、.query()(取得)または.mutation()(更新)で処理を定義します。
// server/trpc.ts
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
const t = initTRPC.create();
export const router = t.router;
export const publicProcedure = t.procedure;
export const appRouter = router({
getUser: publicProcedure
.input(z.object({ id: z.string() }))
.query(({ input }) => {
return { id: input.id, name: 'Yamada' };
}),
addUser: publicProcedure
.input(z.object({ name: z.string().min(1) }))
.mutation(({ input }) => {
return { id: 'u_1', name: input.name };
}),
});
// この型だけをクライアントへ渡す(実体は渡さない)
export type AppRouter = typeof appRouter;
AppRouterは型としてのみエクスポートする点が重要です。クライアントはこの型をimport typeで受け取り、実装コードはバンドルに含まれません。
クライアント:createTRPCClientとhttpBatchLink
クライアントはcreateTRPCClientにAppRouter型を渡すだけで、全Procedureが型付きで補完されます。v10でcreateTRPCProxyClientだった関数は、v11でcreateTRPCClientに改称されました。
// client/index.ts
import { createTRPCClient, httpBatchLink } from '@trpc/client';
import type { AppRouter } from '../server/trpc';
const trpc = createTRPCClient<AppRouter>({
links: [
httpBatchLink({ url: 'http://localhost:3000/api/trpc' }),
],
});
// 補完が効き、戻り値も型付き
const user = await trpc.getUser.query({ id: 'u_1' });
v11ではデータ変換のtransformer設定が、クライアント直下からhttpBatchLinkなどのリンク側へ移動しました。SuperJSONなどを使う場合は各リンクの引数に指定します。
Next.js App RouterでのTanStack React Query統合
Reactからは、React Query経由でuseQuery/useMutationとしてProcedureを呼びます。v11では従来の@trpc/react-queryに加え、新しい@trpc/tanstack-react-query統合が追加され、TanStack Query v5やServer Componentsと組み合わせやすくなりました。Next.jsのApp RouterではAPIルートにfetchRequestHandlerを置き、そこへappRouterを接続します。
// app/api/trpc/[trpc]/route.ts
import { fetchRequestHandler } from '@trpc/server/adapters/fetch';
import { appRouter } from '@/server/trpc';
const handler = (req: Request) =>
fetchRequestHandler({
endpoint: '/api/trpc',
req,
router: appRouter,
createContext: () => ({}),
});
export { handler as GET, handler as POST };
フロントではtrpc.getUser.useQuery()のように、サーバーの関数名がそのままフックとして補完されます。
tRPC v11で追加された主な新機能
v11(現行の安定版は11.18.0)はv10から次の点が変わりました。旧バージョンの記事と挙動が食い違う場合は、まずここを確認してください。
- 非JSON・バイナリ対応:FormDataやBlob・File・Uint8Arrayを送受信可能に。バイナリは
@trpc/server/httpのoctetInputParserで受ける。 - ストリーミング:
httpBatchStreamLinkで、async generatorがyieldする値を逐次ストリーム返却できる。 - サブスクリプション:WebSocketに加えServer-Sent Events(SSE)に対応。ハンドラでジェネレータが使える。
- ルーター短縮記法:プレーンなオブジェクトから
router()で囲まずにサブルーターを組める。 - API改称・削除:
createTRPCProxyClient→createTRPCClient。v9互換の.interop()モードは削除(v9からはv10移行を経る)。
TypeScriptは5.7.2以上が必須になったため、古いTS環境のプロジェクトはアップグレードが前提になります。
モノレポ・T3 Stackでのプロジェクト構成
tRPCの型共有はモノレポと相性が良く、サーバーのAppRouter型を共有パッケージ経由でフロント各アプリから参照する構成が定番です。 turborepoやNx、pnpm workspaceで、packages/api(ルーター定義)とapps/web(フロント)を分け、フロントはimport typeだけをまたいで参照します。実装コードはまたがないため、サーバー専用の依存がフロントのバンドルに漏れません。
この構成を最初から用意したのがT3 Stack(Next.js・tRPC・Prisma・Tailwind・NextAuthのセット)です。tRPCを個別に組み込む前に、まずT3 Stackの雛形で全体の型の流れを掴むと、Router・Context・クライアント設定の関係が理解しやすくなります。
サーバーレス(AWS Lambda等)へのデプロイと注意点
tRPCはランタイムが軽く、AWS Lambdaなどのサーバーレスにも載せられます。@trpc/server/adapters/aws-lambdaのawsLambdaRequestHandlerにルーターを渡し、API GatewayやLambda Function URLから呼び出します。専用ランタイムを持たないためコールドスタートは軽い一方、注意点はhttpBatchLinkによるリクエストのバッチングです。複数のProcedure呼び出しが1つのHTTPリクエストにまとまるため、Lambda側は単一ハンドラで複数操作を処理する前提で設計し、タイムアウトや個別エラーの扱いを確認しておきます。ステートフルなWebSocketサブスクリプションはLambdaと相性が悪いため、常時接続が要るならSSEやコンテナ常駐を検討します。
tRPCを採用すべきでない場面
tRPCは万能ではなく、次の条件に当てはまるなら別の方式が正解です。ここを外して導入すると、型共有という唯一最大の利点が得られないまま複雑さだけが残ります。
- 第三者に公開するAPI:外部の開発者が使う契約には、ドキュメント化・バージョニング・言語非依存が求められる。REST+OpenAPIやGraphQLが適切。
- TypeScript以外のクライアント:iOS/Androidネイティブや他言語のクライアントは
import typeで型を共有できず、tRPCの恩恵がゼロになる。 - 多言語・別リポジトリのバックエンド:サービスごとに言語が違うマイクロサービス間通信は、スキーマ契約を持つgRPCの方が堅い。
- 厳密な契約が要る場面:外部監査や仕様固定が必要なら、明示的なスキーマ(OpenAPI/protobuf)がある方式を選ぶ。
逆に、フロントもバックもTypeScriptで、一つのチームが一つのリポジトリを回す内部向け開発なら、tRPCの費用対効果は非常に高くなります。「使う価値があるか」は、この適用条件に自分のプロジェクトが収まるかで判断してください。
tRPCで遭遇しやすいエラーと対処
導入初期に詰まりやすいのは、多くが型の共有経路に関わる問題です。
- 型が
anyになり補完が効かない:クライアントが実体ではなくimport typeでAppRouterを参照しているか、TSのバージョンとパス設定を確認する。 - 入力バリデーションエラー:
.input()に渡したZodスキーマと実際の送信データの不一致。スキーマ側の必須・型を見直す。 - ContextやNext.js接続の失敗:ハンドラに
createContextを渡し忘れると、認証情報などがProcedureに届かない。 - デプロイ後だけ壊れる:サーバーの実装コードがフロントのバンドルに巻き込まれていないか(型のみ参照になっているか)を確認する。
よくある質問
tRPCとRESTはどちらを使うべきですか?
フロントもバックもTypeScriptで同一リポジトリならtRPC、第三者公開や多言語クライアントが絡むならRESTです。両立も可能で、内部通信はtRPC、公開部分はRESTと使い分ける構成もよく採られます。
tRPCとGraphQLの違いは何ですか?
GraphQLはSDLスキーマとクエリ言語で柔軟な部分取得ができる代わりに学習コストがあります。tRPCはスキーマ言語もクエリ言語もなく、関数呼び出しの形でシンプルですが、部分取得のような柔軟性はありません。
tRPCはgRPCと何が違いますか?
gRPCは.protoと多言語対応・HTTP/2で高速な内部通信を担い、tRPCはTypeScript専用でスキーマもコード生成もありません。多言語ならgRPC、TypeScriptに閉じるならtRPCです。
tRPCは型安全ですが実行時の検証もされますか?
型はビルド時のチェックです。実行時の入力検証は.input()にZodなどのスキーマを渡すことで行われ、不正な入力はランタイムで弾かれます。
tRPCの最新バージョンは何ですか?
2026年時点の安定版はv11系(11.18.0)で、TypeScript 5.7.2以上を必要とします。v10からはクライアント関数名の改称やtransformerの移動などの変更があるため、移行時は公式の移行ガイドで最新を確認してください。