trpc について
tRPC v11、TypeScript、Next.js でエンドツーエンドの型安全な API を構築するためのガイダンススキルです。サーバーとクライアントの設定、検証、認可、React Query 連携を扱います。
主な機能
- 01エンドツーエンドの型推論
- 02Zod による入力検証
- 03機能別ルーター構成
- 04ミドルウェアと認可パターン
- 05React Query のデータ機能
活用シーン
- 01ドメイン別の tRPC API 構成
- 02プロシージャ入力の検証
- 03保護プロシージャとミドルウェアの追加
- 04React でのデータ取得と変更
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ mindrally/ skills --skill trpcインストール後、Claude Code で/trpcを入力して読み込まれたか確認します。
詳細
tRPC のベストプラクティス
tRPC v11、TypeScript、Next.js 開発のエキスパートです。tRPC を使うと、スキーマやコード生成、実行時エラーを必要とせず、エンドツーエンドで型安全な API を構築して利用できます。
要件
- TypeScript >= 5.7.2
- TypeScript の厳格モードを有効にする
プロジェクト構成
src/
pages/
_app.tsx # createTRPCNext setup
api/
trpc/
[trpc].ts # tRPC HTTP handler
server/
routers/
_app.ts # Main router
[feature].ts # Feature-specific routers
context.ts # App context
trpc.ts # Procedure helpers
utils/
trpc.ts # Typesafe React hooks
サーバー側のセットアップ
tRPC バックエンドの初期化
アプリケーションごとに一度だけ tRPC バックエンドを初期化します。再利用可能なルーターとプロシージャのヘルパーをエクスポートします。
// server/trpc.ts
import { initTRPC } from '@trpc/server';
import superjson from 'superjson';
const t = initTRPC.context<Context>().create({
transformer: superjson,
});
export const router = t.router;
export const publicProcedure = t.procedure;
export const protectedProcedure = t.procedure.use(isAuthed);
export const adminProcedure = t.procedure.use(isAdmin);
機能ごとのルーターの作成
ルーターを機能/ドメインごとに整理します。入力のバリデーションには Zod を使用します。
// server/routers/user.ts
import { z } from 'zod';
import { router, publicProcedure, protectedProcedure } from '../trpc';
export const userRouter = router({
getById: publicProcedure
.input(z.object({ id: z.string() }))
.query(async ({ input, ctx }) => {
return ctx.db.user.findUnique({ where: { id: input.id } });
}),
update: protectedProcedure
.input(z.object({
name: z.string().min(1),
email: z.string().email(),
}))
.mutation(async ({ input, ctx }) => {
return ctx.db.user.update({
where: { id: ctx.user.id },
data: input,
});
}),
});
メインアプリルーター
// 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;
クライアント側のセットアップ
tRPC クライアントの設定
// utils/trpc.ts
import { httpBatchLink } from '@trpc/client';
import { createTRPCNext } from '@trpc/next';
import superjson from 'superjson';
import type { AppRouter } from '../server/routers/_app';
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 const trpc = createTRPCNext<AppRouter>({
config() {
return {
transformer: superjson,
links: [
httpBatchLink({
url: `${getBaseUrl()}/api/trpc`,
maxURLLength: 2083,
}),
],
};
},
ssr: false,
});
主なベストプラクティス
1. 入力のバリデーション
型安全性を確保し、すべてのプロシージャ入力を実行時にバリデーションするため、必ず Zod を使用します。
.input(z.object({
email: z.string().email(),
age: z.number().min(0).max(120),
}))
2. ルーターの構成
単一の巨大なルーターではなく、機能/ドメインごとにルーターを構成します。各機能には専用のルーターファイルを用意します。
3. ミドルウェアの実装
認証、ロギング、横断的な関心事に対応するミドルウェアを実装します。
const isAuthed = t.middleware(({ ctx, next }) => {
if (!ctx.user) {
throw new TRPCError({ code: 'UNAUTHORIZED' });
}
return next({ ctx: { user: ctx.user } });
});
4. エラー処理
一貫性があり、情報が明確なエラーレスポンスを返すには、TRPCError を使用します。
import { TRPCError } from '@trpc/server';
throw new TRPCError({
code: 'NOT_FOUND',
message: 'User not found',
});
5. データトランスフォーマー
日付、Map、Set、その他の JavaScript 型を自動的にシリアライズするため、SuperJSON を適用します。
import superjson from 'superjson';
const t = initTRPC.create({
transformer: superjson,
});
6. React Query との統合
データ取得、ミューテーション、キャッシュには、tRPC に組み込まれている React Query ユーティリティを活用します。
// Queries
const { data, isLoading } = trpc.user.getById.useQuery({ id: '123' });
// Mutations
const mutation = trpc.user.update.useMutation({
onSuccess: () => {
utils.user.getById.invalidate();
},
});
// Prefetching
await utils.user.getById.prefetch({ id: '123' });
7. コンテキストの作成
コンテキストを介して、リソース(データベース接続、ユーザーセッション)をプロシージャ間で共有します。
// server/context.ts
export async function createContext({ req, res }: CreateNextContextOptions) {
const user = await getUser(req);
return {
db: prisma,
user,
req,
res,
};
}
export type Context = Awaited<ReturnType<typeof createContext>>;
8. 型のエクスポート
クライアントコードには型シグネチャのみをエクスポートし、ルーターの実装は決してエクスポートしません。
// Only export the type
export type AppRouter = typeof appRouter;
// Never export the actual router to client code
9. 認可レベル
認可レベルごとに異なるプロシージャ型を作成します。
export const publicProcedure = t.procedure;
export const protectedProcedure = t.procedure.use(isAuthed);
export const adminProcedure = t.procedure.use(isAuthed).use(isAdmin);
10. パフォーマンスの最適化
- httpBatchLink でリクエストのバッチ処理を有効にする
- URL の長さに関する問題を防ぐため、maxURLLength を設定する
- 静的データや予測可能なデータに対してプリフェッチを実装する
- React Query のキャッシュ戦略を使用する
避けるべきアンチパターン
any型を使用しないでください。tRPC の完全な型推論を活用します- 入力のバリデーションを省略しないでください
- 内部エラーをクライアントに公開しないでください
- サーバーコードとクライアントコードを混在させないでください
- 巨大すぎるルーターを作成しないでください。機能ごとに分割します