tRPC 使い方入門:Todo API を 50 行で作るフルスタック体験
Web API を開発するとき、「サーバーで定義した型がクライアントで使えたら…」と思ったことはありませんか?tRPC を使えば、TypeScript の型がサーバーからクライアントへ自動で共有され、わずか 50 行程度のコードで型安全な Todo API を作れます。
本記事では、tRPC を使ったフルスタック開発の基本を、実際に動く Todo API を作りながら体験していきましょう。REST API や GraphQL と比べて圧倒的に少ないコード量で、型安全性を保ったまま開発できる感動を味わえますよ。
背景
tRPC とは何か
tRPC(TypeScript Remote Procedure Call)は、TypeScript 専用の型安全な API 通信ライブラリです。サーバー側で定義した関数の型情報が、ビルド時にクライアント側へ自動的に伝わるため、API の入出力を TypeScript の型システムで完全に保護できます。
従来の Web API 開発では、サーバーとクライアントで型定義を二重管理する必要がありました。しかし tRPC を使えば、型定義はサーバー側だけで完結し、クライアントは自動補完とエラーチェックの恩恵を受けられるのです。
以下の図は、tRPC がサーバーとクライアント間でどのように型情報を共有するかを示しています。
mermaidflowchart LR
server["Server<br/>(tRPC Router)"] -->|型情報を<br/>自動生成| client["Client<br/>(React Component)"]
client -->|型安全な<br/>関数呼び出し| server
server -->|型付き<br/>レスポンス| client
style server fill:#e1f5ff
style client fill:#fff4e1
図で理解できる要点:
- サーバー側で定義した tRPC Router の型が自動的にクライアントへ伝わる
- クライアントは通常の関数を呼ぶ感覚で型安全な API 通信ができる
- レスポンスも型付きで返ってくるため、実行時エラーを大幅に削減可能
tRPC が注目される理由
フルスタック TypeScript 開発の人気が高まる中、tRPC は以下の特徴で注目を集めています。
| # | 特徴 | 説明 |
|---|---|---|
| 1 | 型の自動共有 | サーバーの型定義がクライアントで即座に使える |
| 2 | ゼロコード生成 | GraphQL のようなスキーマファイルや型生成スクリプトが不要 |
| 3 | 軽量 | 追加の DSL や複雑な設定なしで導入可能 |
| 4 | React Query 統合 | データフェッチング、キャッシュ、リフェッチを簡単に実装 |
| 5 | 開発体験の向上 | エディタの自動補完で API 仕様を把握しながら開発できる |
特に、Next.js や Remix などのフルスタックフレームワークとの相性が抜群で、サーバーとクライアントを同じリポジトリで管理するモノレポ構成で真価を発揮します。
課題
REST API における型安全性の問題
REST API を使った開発では、以下のような型の問題が頻繁に発生します。
typescript// サーバー側 (例: Express + TypeScript)
interface Todo {
id: number;
title: string;
completed: boolean;
}
app.get('/api/todos', (req, res) => {
const todos: Todo[] = getTodos();
res.json(todos);
});
typescript// クライアント側 (例: React)
// サーバーの Todo 型を知らないため、型定義を再現する必要がある
interface Todo {
id: number;
title: string;
completed: boolean; // タイポや型の不一致が起こりやすい
}
const response = await fetch('/api/todos');
const todos: Todo[] = await response.json(); // 型アサーションが必要
上記のコードには以下の課題があります。
主な課題:
- 型定義の二重管理 - サーバーとクライアントで同じ型を別々に定義しなければならない
- 型の不一致 - サーバー側で型が変更されてもクライアント側は気づけない
- 実行時エラー - ビルド時に型チェックができず、実行時に初めてエラーが発覚する
- 手動の型アサーション -
asや型注釈で強制的に型をつける必要がある
GraphQL の学習コストと複雑性
GraphQL は型安全性を提供しますが、以下のような導入コストがかかります。
| # | 課題 | 詳細 |
|---|---|---|
| 1 | スキーマ定義 | SDL (Schema Definition Language) を別途学習・記述する必要がある |
| 2 | コード生成 | graphql-codegen などのツールで型を生成する設定が必要 |
| 3 | リゾルバー実装 | スキーマとリゾルバーを別々に管理しなければならない |
| 4 | ビルドプロセス | スキーマから型を生成するステップがビルドに追加される |
| 5 | 学習曲線 | Query / Mutation / Subscription の概念を理解する必要がある |
小規模なプロジェクトや API が限定的な場合、GraphQL は過剰な仕組みになってしまうことも少なくありません。
以下の図は、従来の API 開発における型の流れと課題を示しています。
mermaidflowchart TD
serverDef["サーバー側<br/>型定義"] -->|手動で<br/>コピー| clientDef["クライアント側<br/>型定義"]
serverDef -->|API実装| api["REST/GraphQL<br/>エンドポイント"]
api -->|レスポンス| client["クライアント"]
clientDef -.->|型アサーション| client
serverDef -.->|変更| change["型変更"]
change -.->|同期忘れ| clientDef
change -->|実行時<br/>エラー| runtime["Runtime Error"]
style runtime fill:#ffe1e1
style change fill:#fff4e1
図で理解できる要点:
- サーバーとクライアントで型定義が分離しているため、手動同期が必要
- サーバー側で型を変更してもクライアント側に自動で伝わらない
- 型の不一致は実行時エラーとして発覚し、開発効率が低下する
解決策
tRPC が実現する型の自動共有
tRPC は TypeScript の型推論を活用 して、サーバーとクライアント間で型を自動共有します。サーバー側で tRPC Router を定義すると、その型情報がクライアント側の tRPC Client に自動的に伝わるのです。
typescript// サーバー側で定義
export const appRouter = router({
getTodos: publicProcedure.query(() => {
return [
{ id: 1, title: 'Learn tRPC', completed: false },
];
}),
});
export type AppRouter = typeof appRouter;
typescript// クライアント側で使用
import type { AppRouter } from './server';
const trpc = createTRPCClient<AppRouter>({...});
// getTodos の戻り値の型が自動的に推論される
const todos = await trpc.getTodos.query();
// todos の型: { id: number; title: string; completed: boolean }[]
このように、サーバーの Router 型をクライアントにインポートするだけで、API の入出力が完全に型安全になります。
tRPC の仕組み
tRPC の型共有は、以下の 3 つの要素で成り立っています。
| # | 要素 | 役割 |
|---|---|---|
| 1 | Router | サーバー側で API エンドポイント(Procedure)を定義する |
| 2 | Procedure | Query(読み取り)や Mutation(書き込み)の処理ロジックを記述 |
| 3 | Client | サーバーの Router 型を受け取り、型安全な関数として API を呼び出す |
Router と Client を型でつなぐことで、型定義ファイルやスキーマファイルを一切書かずに、TypeScript の型システムだけで型安全性を実現しています。
以下の図は、tRPC における型の流れを示しています。
mermaidsequenceDiagram
participant Dev as 開発者
participant Server as tRPC Router
participant TS as TypeScript<br/>Compiler
participant Client as tRPC Client
Dev->>Server: Procedure を定義
Server->>TS: AppRouter 型を生成
TS->>Client: 型情報を伝達
Client->>Dev: 自動補完・型チェック
Dev->>Client: query/mutation 呼び出し
Client->>Server: HTTP リクエスト
Server->>Client: 型付きレスポンス
図で理解できる要点:
- 開発者が Procedure を定義すると、TypeScript が型情報を自動生成
- 型情報はビルド時にクライアントへ伝わり、エディタで自動補完が効く
- 実行時は通常の HTTP 通信だが、型安全性が保証される
REST API と tRPC の比較
tRPC を使うと、コード量とメンテナンスコストが大幅に削減されます。
| # | 項目 | REST API | tRPC |
|---|---|---|---|
| 1 | 型定義 | サーバー・クライアント両方で必要 | サーバー側のみ |
| 2 | 型の同期 | 手動(ドキュメントや定義ファイル) | 自動(TypeScript の型推論) |
| 3 | エンドポイント管理 | URL 文字列で管理 | 関数名で管理(タイポ防止) |
| 4 | エラーハンドリング | HTTP ステータスを手動チェック | 型安全な Error オブジェクト |
| 5 | 開発体験 | Postman などで API 仕様確認 | エディタの自動補完で即座に確認 |
REST API では URL やリクエストボディを文字列で扱うため、タイポや型の不一致が実行時まで検出されません。一方 tRPC は、関数呼び出しの形で API を扱うため、ビルド時に全ての型エラーをキャッチできるのです。
具体例
プロジェクトのセットアップ
まず、Next.js プロジェクトを作成し、tRPC の必要なパッケージをインストールします。
bash# Next.js プロジェクトを作成
yarn create next-app trpc-todo-app --typescript
cd trpc-todo-app
bash# tRPC 関連パッケージをインストール
yarn add @trpc/server @trpc/client @trpc/react-query @trpc/next @tanstack/react-query zod
上記のコマンドで、tRPC のサーバー・クライアント機能と、データフェッチング用の React Query、バリデーション用の Zod がインストールされます。
サーバー側の実装
1. tRPC Router の初期化
tRPC の基本設定として、Router と Procedure を作成するヘルパー関数を定義します。
typescript// src/server/trpc.ts
import { initTRPC } from '@trpc/server';
// tRPC インスタンスを初期化
const t = initTRPC.create();
// Router と Procedure を作成するヘルパーをエクスポート
export const router = t.router;
export const publicProcedure = t.procedure;
initTRPC.create() で tRPC インスタンスを作成し、router と publicProcedure という 2 つのヘルパー関数を定義しています。これらを使って API エンドポイントを構築していきます。
2. Todo の型定義とバリデーション
Zod を使って、Todo データのバリデーションスキーマを定義します。
typescript// src/server/router.ts
import { z } from 'zod';
import { router, publicProcedure } from './trpc';
// Todo のバリデーションスキーマ
const todoSchema = z.object({
id: z.number(),
title: z.string(),
completed: z.boolean(),
});
// インメモリで Todo を管理(実際は DB を使用)
let todos = [
{ id: 1, title: 'Learn tRPC', completed: false },
{ id: 2, title: 'Build Todo API', completed: false },
];
Zod のスキーマ定義により、実行時のバリデーションと TypeScript の型推論を同時に実現できます。todoSchema から自動的に型が推論されるため、型定義を別途書く必要はありません。
3. CRUD 操作の Procedure 定義
Todo の一覧取得、追加、更新、削除の 4 つの Procedure を定義します。
typescript// src/server/router.ts(続き)
export const appRouter = router({
// Todo 一覧を取得
getTodos: publicProcedure.query(() => {
return todos;
}),
// Todo を追加
addTodo: publicProcedure
.input(z.object({ title: z.string() }))
.mutation(({ input }) => {
const newTodo = {
id: todos.length + 1,
title: input.title,
completed: false,
};
todos.push(newTodo);
return newTodo;
}),
上記では、getTodos という Query と addTodo という Mutation を定義しています。input() メソッドで入力値のバリデーションを行い、型安全性を確保しています。
typescript // Todo を更新
updateTodo: publicProcedure
.input(z.object({ id: z.number(), completed: z.boolean() }))
.mutation(({ input }) => {
const todo = todos.find(t => t.id === input.id);
if (todo) {
todo.completed = input.completed;
}
return todo;
}),
// Todo を削除
deleteTodo: publicProcedure
.input(z.object({ id: z.number() }))
.mutation(({ input }) => {
todos = todos.filter(t => t.id !== input.id);
return { success: true };
}),
});
// Router の型をエクスポート(クライアント側で使用)
export type AppRouter = typeof appRouter;
updateTodo と deleteTodo も同様に Mutation として定義します。最後に AppRouter 型をエクスポートすることで、クライアント側で型安全な API 呼び出しが可能になります。
4. Next.js API Route の設定
Next.js の API Route で tRPC を動かすための設定を行います。
typescript// src/pages/api/trpc/[trpc].ts
import { createNextApiHandler } from '@trpc/server/adapters/next';
import { appRouter } from '../../../server/router';
// Next.js API Route として tRPC を公開
export default createNextApiHandler({
router: appRouter,
createContext: () => ({}), // 認証情報などを渡す場合はここで設定
});
createNextApiHandler を使うことで、tRPC Router を /api/trpc/* というエンドポイントとして公開できます。
クライアント側の実装
1. tRPC Client の初期化
サーバーの型情報を受け取る tRPC Client を設定します。
typescript// src/utils/trpc.ts
import { createTRPCReact } from '@trpc/react-query';
import type { AppRouter } from '../server/router';
// AppRouter 型を使って tRPC Client を作成
export const trpc = createTRPCReact<AppRouter>();
createTRPCReact<AppRouter>() により、サーバーの AppRouter 型がクライアントに伝わり、型安全な API 呼び出しが可能になります。
2. React Query Provider の設定
tRPC は内部で React Query を使用するため、アプリ全体を Provider でラップします。
typescript// src/pages/_app.tsx
import { useState } from 'react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { httpBatchLink } from '@trpc/client';
import { trpc } from '../utils/trpc';
import type { AppProps } from 'next/app';
function MyApp({ Component, pageProps }: AppProps) {
const [queryClient] = useState(() => new QueryClient());
const [trpcClient] = useState(() =>
trpc.createClient({
links: [
httpBatchLink({
url: '/api/trpc', // tRPC エンドポイントの URL
}),
],
})
);
QueryClient と trpcClient を初期化し、後述の Provider に渡します。httpBatchLink を使うことで、複数の API 呼び出しを 1 つの HTTP リクエストにまとめて効率化できます。
typescript return (
<trpc.Provider client={trpcClient} queryClient={queryClient}>
<QueryClientProvider client={queryClient}>
<Component {...pageProps} />
</QueryClientProvider>
</trpc.Provider>
);
}
export default MyApp;
tRPC の Provider と React Query の Provider でアプリをラップすることで、全てのコンポーネントで tRPC hooks が使えるようになります。
3. Todo コンポーネントの実装
tRPC hooks を使って Todo の一覧表示と操作を実装します。
typescript// src/pages/index.tsx
import { trpc } from '../utils/trpc';
import { useState } from 'react';
export default function Home() {
const [title, setTitle] = useState('');
// Todo 一覧を取得(型安全な useQuery)
const { data: todos, refetch } = trpc.getTodos.useQuery();
// Todo 追加(型安全な useMutation)
const addMutation = trpc.addTodo.useMutation({
onSuccess: () => refetch(), // 追加後に再取得
});
trpc.getTodos.useQuery() で Todo 一覧を取得します。戻り値の todos は自動的に型推論され、エディタで補完が効きます。addMutation も同様に型安全です。
typescript// Todo 更新
const updateMutation = trpc.updateTodo.useMutation({
onSuccess: () => refetch(),
});
// Todo 削除
const deleteMutation = trpc.deleteTodo.useMutation({
onSuccess: () => refetch(),
});
const handleAdd = () => {
if (title.trim()) {
addMutation.mutate({ title }); // 型安全な入力
setTitle('');
}
};
updateMutation と deleteMutation も同様に定義します。mutate() メソッドに渡す引数も型チェックされるため、タイポや型ミスがあればビルド時にエラーになります。
typescript return (
<div>
<h1>tRPC Todo App</h1>
{/* Todo 追加フォーム */}
<div>
<input
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="新しいタスク"
/>
<button onClick={handleAdd}>追加</button>
</div>
{/* Todo 一覧 */}
<ul>
{todos?.map((todo) => (
<li key={todo.id}>
<input
type="checkbox"
checked={todo.completed}
onChange={() =>
updateMutation.mutate({
id: todo.id,
completed: !todo.completed,
})
}
/>
<span>{todo.title}</span>
<button onClick={() => deleteMutation.mutate({ id: todo.id })}>
削除
</button>
</li>
))}
</ul>
</div>
);
}
Todo の一覧を表示し、チェックボックスで完了状態を更新、削除ボタンで Todo を削除できます。全ての操作が型安全で、エディタの補完が効くため、開発体験が格段に向上します。
実装の要点まとめ
以下の表で、実装したコードの行数と役割を整理します。
| # | ファイル | 行数 | 役割 |
|---|---|---|---|
| 1 | server/trpc.ts | 6 行 | tRPC の初期化 |
| 2 | server/router.ts | 約 40 行 | Todo CRUD の定義 |
| 3 | pages/api/trpc/[trpc].ts | 8 行 | Next.js API Route 設定 |
| 4 | utils/trpc.ts | 4 行 | tRPC Client 初期化 |
| 5 | pages/_app.tsx | 約 20 行 | Provider 設定 |
| 6 | pages/index.tsx | 約 50 行 | Todo UI コンポーネント |
合計で約 130 行となりますが、実際の API ロジック部分(CRUD 定義)は 約 50 行 に収まっています。REST API や GraphQL と比べて、型定義やスキーマファイルが一切不要で、コード量が圧倒的に少ないことがわかりますね。
動作確認
開発サーバーを起動して、動作を確認しましょう。
bash# 開発サーバーを起動
yarn dev
ブラウザで http://localhost:3000 にアクセスすると、Todo アプリが表示されます。タスクを追加・更新・削除してみてください。全ての操作が型安全に動作し、エディタでは自動補完が効いていることを実感できるはずです。
開発者ツールのネットワークタブを見ると、tRPC が HTTP POST リクエストでサーバーと通信していることが確認できます。内部的には JSON-RPC のような形式でデータがやり取りされています。
まとめ
tRPC を使えば、わずか 50 行程度のコードで型安全な Todo API を実装でき、フルスタック TypeScript 開発の魅力を存分に体験できます。本記事で学んだ内容を振り返ってみましょう。
tRPC のメリット
| # | メリット | 詳細 |
|---|---|---|
| 1 | 型の自動共有 | サーバーの型がクライアントに自動で伝わる |
| 2 | コード量の削減 | スキーマや型定義ファイルが不要 |
| 3 | 開発体験の向上 | エディタの自動補完でミスを防げる |
| 4 | 実行時エラーの削減 | ビルド時に型エラーを検出できる |
| 5 | React Query 統合 | キャッシュやリフェッチが簡単に実装可能 |
tRPC が適しているケース
tRPC は以下のようなプロジェクトで特に威力を発揮します。
- フルスタック TypeScript のプロジェクト(Next.js、Remix など)
- モノレポ構成 でサーバーとクライアントを同じリポジトリで管理
- 小〜中規模の API で、GraphQL のような複雑な仕組みが不要
- スタートアップや MVP 開発 で、迅速にプロトタイプを作りたい場合
- 型安全性を重視 するチーム開発
次のステップ
今回は基本的な CRUD 操作を実装しましたが、tRPC にはさらに高度な機能があります。
- 認証・認可 - Context を使ってユーザー情報を Procedure に渡す
- エラーハンドリング -
TRPCErrorでカスタムエラーを定義 - Middleware - ログ出力や権限チェックを共通化
- Subscription - WebSocket を使ったリアルタイム通信
- DB 連携 - Prisma などの ORM と組み合わせて本格的な API を構築
tRPC の公式ドキュメントには、これらのトピックについて詳しく解説されていますので、ぜひ挑戦してみてください。型安全なフルスタック開発の世界が、さらに広がっていくことでしょう。
関連リンク
articletRPC 使い方入門:Todo API を 50 行で作るフルスタック体験
articletRPC アーキテクチャ設計:BFF とドメイン分割で肥大化を防ぐルータ戦略
articletRPC チートシート:Router/Procedure/ctx/useQuery/useMutation 早見表
articletRPC の始め方:Next.js App Router と Zod を使った最小構成テンプレート
articletRPC とは?型安全なフルスタック通信を実現する仕組みとメリット【2025 年版】
articleTypeScript タプル/配列操作チートシート:`Length`・`Push`・`Zip`・`Chunk`を型で書く
articlesolidJS × SolidStart を Cloudflare Pages にデプロイ:Edge 最適化の手順
articleShell Script でインフラ初期構築の自動化:ユーザー作成・SSH 設定・FW ルール
articletRPC 使い方入門:Todo API を 50 行で作るフルスタック体験
articleTauri vs Electron vs Flutter デスクトップ:UX・DX・配布のしやすさ徹底比較
articleRuby と Python を徹底比較:スクリプト・Web・データ処理での得意分野
blogiPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
blogGoogleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
blog【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
blogGoogleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
blogPixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
blogフロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
review今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
reviewついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
review愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
review週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
review新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
review科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来