はじめに
フロントエンドとバックエンドで型定義を二重管理するの、正直しんどくないですか。
REST APIを作ってOpenAPIでスキーマ書いて、コード生成して、型定義を更新して...このサイクル、何度繰り返したかわかりません。サーバー側で型を変えたのにクライアント側で気づかず、ランタイムエラーが出るという話もよくあります。
そんな悩みを根本から解決してくれるのがtRPCというライブラリです。
tRPCはGitHubで39,100スター以上を獲得している、TypeScriptのための完全型安全なAPIフレームワーク。「Move Fast and Break Nothing」というスローガンの通り、開発速度を落とさずに型安全性を手に入れられます。
Google、Netflix、PayPalなどのFortune 500企業でも採用されているという実績も心強いですね。
tRPCとは
tRPCは、スキーマ定義やコード生成なしで完全なエンドツーエンド型安全を実現するTypeScript専用のRPCフレームワークです。バージョン11.7.2(2025年12月時点)がリリースされており、494人のコントリビューターによって活発に開発が続いています。
最大の特徴は、サーバー側で定義した型がクライアント側にそのまま伝播すること。ファイルを保存する前にTypeScriptがエラーを警告してくれるので、ランタイムエラーを大幅に減らせます。
個人的には、これがTypeScriptでフルスタック開発する上での「正解」だと思っています。
公式サイト: https://trpc.io GitHub: https://github.com/trpc/trpc
特徴・メリット
1. 完全なエンドツーエンド型安全
これ、意外と他のライブラリでは実現できていないんですよ。
// サーバー側で型を定義
const appRouter = router({
user: router({
byId: publicProcedure
.input(z.object({ id: z.string() }))
.query(async ({ input }) => {
return { id: input.id, name: 'easegis', email: 'example@test.com' }
}),
}),
})
// クライアント側 - 自動的に型が推論される
const user = await trpc.user.byId.query({ id: '1' })
// user の型は { id: string, name: string, email: string } と推論される
サーバー側で返り値の型を変えたら、クライアント側でも即座にTypeScriptエラーが出る。この安心感は一度体験すると手放せません。
2. スキーマ定義・コード生成が不要
GraphQLやOpenAPIのように、別ファイルでスキーマを定義する必要がありません。TypeScriptの型システムをそのまま活用します。
// これだけでAPIの「仕様」が完成する
export const userRouter = router({
list: publicProcedure.query(async () => {
return await db.user.findMany()
}),
create: publicProcedure
.input(z.object({
name: z.string().min(1),
email: z.string().email(),
}))
.mutation(async ({ input }) => {
return await db.user.create({ data: input })
}),
})
コード生成のビルドステップも不要。開発サーバーを再起動する必要もない。時短になりますね。
3. ゼロ依存で軽量
tRPCのコアは依存関係ゼロで、クライアント側のバンドルサイズは最小限です。パフォーマンスを気にするプロジェクトでも安心して導入できます。
4. 豊富なフレームワーク対応
React、Next.js、Express、Fastify、AWS Lambdaなど、主要なフレームワークやランタイムに対応しています。
// Next.js App Router
import { fetchRequestHandler } from '@trpc/server/adapters/fetch'
// Express
import { createExpressMiddleware } from '@trpc/server/adapters/express'
// Fastify
import { fastifyTRPCPlugin } from '@trpc/server/adapters/fastify'
既存のプロジェクトにも段階的に導入できるのが嬉しいポイントです。
5. リクエストバッチングとサブスクリプション
複数のAPIコールを自動的にバッチ処理してくれる機能があります。また、WebSocketを使ったリアルタイム通信(サブスクリプション)もサポートしています。
// 複数のクエリが自動的にバッチされる
const [users, posts] = await Promise.all([
trpc.user.list.query(),
trpc.post.list.query(),
])
// 実際のHTTPリクエストは1回だけ
30代になって思うのは、こういう「賢い最適化」を勝手にやってくれるライブラリは本当にありがたいということ。
インストール方法
基本パッケージ
npm install @trpc/server @trpc/client
yarn、pnpm、bunでもインストール可能です。
要件
- TypeScript >= 5.7.2が必須
tsconfig.jsonで"strict": trueを推奨
{
"compilerOptions": {
"strict": true
}
}
Next.js用の追加パッケージ
npm install @trpc/server @trpc/client @trpc/react-query @tanstack/react-query zod
スターターテンプレート
Next.js + Prismaの完全な例から始めたい場合は:
pnpm create next-app --example https://github.com/trpc/trpc --example-path examples/next-prisma-starter
基本的な使い方
1. tRPCの初期化
// server/trpc.ts
import { initTRPC } from '@trpc/server'
const t = initTRPC.create()
export const router = t.router
export const publicProcedure = t.procedure
2. ルーターの作成
// server/routers/user.ts
import { z } from 'zod'
import { router, publicProcedure } from '../trpc'
export const userRouter = router({
// クエリ(データ取得)
list: publicProcedure.query(async () => {
// DBからユーザー一覧を取得
return [
{ id: '1', name: 'easegis', email: 'example@test.com' },
{ id: '2', name: 'taro', email: 'taro@test.com' },
]
}),
// 入力バリデーション付きクエリ
byId: publicProcedure
.input(z.object({ id: z.string() }))
.query(async ({ input }) => {
return { id: input.id, name: 'easegis', email: 'example@test.com' }
}),
// ミューテーション(データ更新)
create: publicProcedure
.input(z.object({
name: z.string().min(1, '名前は必須です'),
email: z.string().email('有効なメールアドレスを入力してください'),
}))
.mutation(async ({ input }) => {
// DBにユーザーを作成
return { id: '3', ...input }
}),
})
3. アプリルーターの統合
// server/routers/_app.ts
import { router } from '../trpc'
import { userRouter } from './user'
import { postRouter } from './post'
export const appRouter = router({
user: userRouter,
post: postRouter,
})
// クライアント用の型をエクスポート
export type AppRouter = typeof appRouter
4. HTTPサーバーのセットアップ
// server/index.ts
import { createHTTPServer } from '@trpc/server/adapters/standalone'
import { appRouter } from './routers/_app'
const server = createHTTPServer({
router: appRouter,
})
server.listen(3000)
console.log('Server listening on http://localhost:3000')
5. クライアントの作成
// client/trpc.ts
import { createTRPCClient, httpBatchLink } from '@trpc/client'
import type { AppRouter } from '../server/routers/_app'
export const trpc = createTRPCClient<AppRouter>({
links: [
httpBatchLink({
url: 'http://localhost:3000',
}),
],
})
6. クライアントからAPIを呼び出す
// 完全な型安全性と自動補完が効く
const users = await trpc.user.list.query()
// users: { id: string, name: string, email: string }[]
const user = await trpc.user.byId.query({ id: '1' })
// user: { id: string, name: string, email: string }
const newUser = await trpc.user.create.mutate({
name: 'new user',
email: 'new@test.com',
})
// newUser: { id: string, name: string, email: string }
実践的なユースケース
1. Next.js App Routerとの統合
// app/api/trpc/[trpc]/route.ts
import { fetchRequestHandler } from '@trpc/server/adapters/fetch'
import { appRouter } from '@/server/routers/_app'
const handler = (req: Request) =>
fetchRequestHandler({
endpoint: '/api/trpc',
req,
router: appRouter,
createContext: () => ({}),
})
export { handler as GET, handler as POST }
// lib/trpc.ts(React Query統合)
import { createTRPCReact } from '@trpc/react-query'
import type { AppRouter } from '@/server/routers/_app'
export const trpc = createTRPCReact<AppRouter>()
// components/UserList.tsx
'use client'
import { trpc } from '@/lib/trpc'
export function UserList() {
const { data: users, isLoading } = trpc.user.list.useQuery()
if (isLoading) return <div>Loading...</div>
return (
<ul>
{users?.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)
}
2. 認証付きプロシージャ
// server/trpc.ts
import { initTRPC, TRPCError } from '@trpc/server'
import { Context } from './context'
const t = initTRPC.context<Context>().create()
export const router = t.router
export const publicProcedure = t.procedure
// 認証必須のプロシージャ
export const protectedProcedure = t.procedure.use(async ({ ctx, next }) => {
if (!ctx.session?.user) {
throw new TRPCError({ code: 'UNAUTHORIZED' })
}
return next({
ctx: {
...ctx,
user: ctx.session.user,
},
})
})
// server/routers/admin.ts
export const adminRouter = router({
// 認証済みユーザーのみアクセス可能
dashboard: protectedProcedure.query(async ({ ctx }) => {
return { message: `Welcome, ${ctx.user.name}!` }
}),
})
3. エラーハンドリング
import { TRPCError } from '@trpc/server'
export const userRouter = router({
byId: publicProcedure
.input(z.object({ id: z.string() }))
.query(async ({ input }) => {
const user = await db.user.findUnique({ where: { id: input.id } })
if (!user) {
throw new TRPCError({
code: 'NOT_FOUND',
message: 'ユーザーが見つかりません',
})
}
return user
}),
})
// クライアント側でのエラーハンドリング
try {
const user = await trpc.user.byId.query({ id: 'invalid' })
} catch (error) {
if (error instanceof TRPCClientError) {
console.log(error.data?.code) // 'NOT_FOUND'
console.log(error.message) // 'ユーザーが見つかりません'
}
}
4. ミドルウェアでのログ出力
const loggerMiddleware = t.middleware(async ({ path, type, next }) => {
const start = Date.now()
const result = await next()
const duration = Date.now() - start
console.log(`${type} ${path} - ${duration}ms`)
return result
})
export const loggedProcedure = t.procedure.use(loggerMiddleware)
注意点
いいことばかり書いてきましたが、注意点もあります。
1. TypeScript必須
tRPCの恩恵を受けるには、フロントエンドとバックエンドの両方がTypeScriptである必要があります。JavaScriptプロジェクトには向いていません。
2. モノレポとの相性
型をクライアントとサーバーで共有するため、モノレポ構成が推奨されます。別リポジトリでフロントとバックを管理している場合は、型の共有方法を工夫する必要があります。
3. REST APIとの違い
tRPCは厳密にはRESTではありません。HTTPの動詞(GET、POSTなど)ではなく、プロシージャ名で操作を区別します。既存のREST APIとの統合や、外部公開用APIには向いていない場合があります。
4. 学習コスト
Zodによるバリデーション、ミドルウェア、コンテキストなど、独自の概念がいくつかあります。ただ、TypeScriptに慣れていれば数時間で基本は押さえられるレベルです。
まとめ
正直なところ、TypeScriptでフルスタック開発するならtRPC一択ですね。
以下の条件に当てはまるなら、試してみる価値は間違いなくあります:
- フロントエンドとバックエンドの両方がTypeScript
- API定義の二重管理にうんざりしている
- 型安全性を妥協したくない
- Next.jsやReactを使っている
- 開発速度を上げたい
月間650万ダウンロード以上、93,200以上のプロジェクトで使用されているという実績が、その価値を証明しています。
個人的には、tRPCを導入してからAPIの型エラーで悩むことがほぼなくなりました。サーバー側を変更したらクライアント側でTypeScriptが教えてくれる。この安心感でQOL上がること間違いなしです。
まずは小さなプロジェクトで試してみてください。一度この体験をすると、もう戻れなくなります。
公式サイト: https://trpc.io GitHub: https://github.com/trpc/trpc ドキュメント: https://trpc.io/docs