tRPC v11入門 — エンドツーエンド型安全APIの構築ガイド
tRPCは、TypeScriptによるエンドツーエンド型安全なAPIを実現するフレームワークです。REST APIやGraphQLと異なり、スキーマ定義や型生成が不要で、TypeScriptの型システムを直接活用できます。
この記事では、tRPC v11の基本から実践的な使い方まで、順を追って解説します。
tRPCとは
tRPC(TypeScript Remote Procedure Call)は、クライアントとサーバー間で型情報を共有し、コンパイル時に型エラーを検出できるフレームワークです。
主な特徴:
- 型安全: サーバー側の変更が即座にクライアント側に反映
- コード生成不要: TypeScriptの型推論を活用
- 軽量: ランタイムオーバーヘッドが小さい
- 柔軟: 既存のExpressやNext.jsに統合可能
インストールとセットアップ
まず、必要なパッケージをインストールします。
npm install @trpc/server@next @trpc/client@next @trpc/react-query@next
npm install @tanstack/react-query@latest zod
サーバー側の設定
tRPCルーターを定義します。
// 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({
hello: publicProcedure
.input(z.object({ name: z.string() }))
.query(({ input }) => {
return { greeting: `Hello, ${input.name}!` };
}),
createPost: publicProcedure
.input(z.object({
title: z.string().min(1),
content: z.string(),
}))
.mutation(async ({ input }) => {
// データベースへの保存処理
const post = await db.post.create({ data: input });
return post;
}),
});
export type AppRouter = typeof appRouter;
Next.js App Routerとの統合
Next.js 13+のApp Routerで使用する場合、Route Handlerを作成します。
// 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クライアントとReact Queryを統合します。
// lib/trpc.ts
import { createTRPCReact } from '@trpc/react-query';
import { httpBatchLink } from '@trpc/client';
import type { AppRouter } from '@/server/trpc';
export const trpc = createTRPCReact<AppRouter>();
export function TRPCProvider({ children }: { children: React.ReactNode }) {
const [queryClient] = useState(() => new QueryClient());
const [trpcClient] = useState(() =>
trpc.createClient({
links: [
httpBatchLink({
url: '/api/trpc',
}),
],
})
);
return (
<trpc.Provider client={trpcClient} queryClient={queryClient}>
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
</trpc.Provider>
);
}
Zodによるバリデーション
Zodスキーマを使用して、入力値を検証します。
import { z } from 'zod';
const userSchema = z.object({
email: z.string().email('有効なメールアドレスを入力してください'),
password: z.string().min(8, 'パスワードは8文字以上必要です'),
age: z.number().int().min(0).max(120),
});
export const authRouter = router({
register: publicProcedure
.input(userSchema)
.mutation(async ({ input }) => {
// input は型安全に検証済み
const user = await createUser(input);
return { success: true, userId: user.id };
}),
});
Reactコンポーネントでの使用
クライアント側でtRPCフックを使用します。
'use client';
import { trpc } from '@/lib/trpc';
export function UserProfile({ userId }: { userId: string }) {
const { data, isLoading, error } = trpc.user.getById.useQuery({ id: userId });
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div>
<h1>{data.name}</h1>
<p>{data.email}</p>
</div>
);
}
ミューテーションの使用
データ変更にはミューテーションを使用します。
export function CreatePostForm() {
const utils = trpc.useContext();
const createPost = trpc.post.create.useMutation({
onSuccess: () => {
// キャッシュを無効化して再取得
utils.post.list.invalidate();
},
});
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
await createPost.mutateAsync({
title: formData.get('title') as string,
content: formData.get('content') as string,
});
};
return (
<form onSubmit={handleSubmit}>
<input name="title" required />
<textarea name="content" required />
<button type="submit" disabled={createPost.isLoading}>
{createPost.isLoading ? '作成中...' : '作成'}
</button>
</form>
);
}
ミドルウェアとコンテキスト
認証などの共通処理はミドルウェアで実装します。
import { TRPCError } from '@trpc/server';
const t = initTRPC.context<Context>().create();
const isAuthed = t.middleware(({ ctx, next }) => {
if (!ctx.user) {
throw new TRPCError({ code: 'UNAUTHORIZED' });
}
return next({
ctx: {
user: ctx.user, // 認証済みユーザー
},
});
});
export const protectedProcedure = t.procedure.use(isAuthed);
export const userRouter = router({
me: protectedProcedure.query(({ ctx }) => {
// ctx.user は必ず存在する
return ctx.user;
}),
});
サブスクリプション(リアルタイム)
WebSocketを使用したリアルタイム通信も可能です。
import { observable } from '@trpc/server/observable';
export const chatRouter = router({
onMessage: publicProcedure.subscription(() => {
return observable<Message>((emit) => {
const onMessage = (message: Message) => {
emit.next(message);
};
eventEmitter.on('message', onMessage);
return () => {
eventEmitter.off('message', onMessage);
};
});
}),
});
クライアント側:
export function ChatRoom() {
trpc.chat.onMessage.useSubscription(undefined, {
onData: (message) => {
console.log('New message:', message);
},
});
return <div>Chat Room</div>;
}
エラーハンドリング
tRPCは構造化されたエラーハンドリングをサポートします。
import { TRPCError } from '@trpc/server';
export const postRouter = router({
delete: protectedProcedure
.input(z.object({ id: z.string() }))
.mutation(async ({ input, ctx }) => {
const post = await db.post.findUnique({ where: { id: input.id } });
if (!post) {
throw new TRPCError({
code: 'NOT_FOUND',
message: '投稿が見つかりません',
});
}
if (post.authorId !== ctx.user.id) {
throw new TRPCError({
code: 'FORBIDDEN',
message: '削除権限がありません',
});
}
await db.post.delete({ where: { id: input.id } });
return { success: true };
}),
});
パフォーマンス最適化
バッチリクエスト
tRPCは複数のリクエストを自動的にバッチ処理します。
const trpcClient = trpc.createClient({
links: [
httpBatchLink({
url: '/api/trpc',
maxURLLength: 2083, // URL長制限
}),
],
});
データプリフェッチ
Server Componentsでデータをプリフェッチできます。
import { createServerSideHelpers } from '@trpc/react-query/server';
export default async function PostPage({ params }: { params: { id: string } }) {
const helpers = createServerSideHelpers({
router: appRouter,
ctx: {},
});
await helpers.post.getById.prefetch({ id: params.id });
return (
<HydrateClient>
<PostDetail id={params.id} />
</HydrateClient>
);
}
まとめ
tRPC v11は、フルスタックTypeScriptアプリケーションで型安全なAPIを実現する強力なツールです。
主なメリット:
- コンパイル時の型チェック
- スキーマ定義・コード生成不要
- React Queryとのシームレスな統合
- Next.js App Routerへの対応
tRPCを使えば、APIの型ミスマッチによるバグを大幅に削減し、開発体験を向上させることができます。