tRPC とは何か — REST や GraphQL との違い
フルスタック TypeScript 開発において、API レイヤーの型安全性は長年の課題でしました。REST API ではクライアントとサーバー間で型の整合性を手動で保つ必要があり、GraphQL では codegen ツールによる型生成が必要です。tRPC はこの問題を根本から解決するライブラリで、サーバーで定義した型がクライアントに自動で伝播します。
tRPC の最大の特徴は「ゼロコードジェネレーション」です。スキーマ定義ファイルや型生成コマンドを実行する必要がなく、TypeScript の型推論だけで API 全体の型安全性が担保されます。Antigravity の AI エージェントはこの仕組みを理解しており、tRPC のルーター定義からクライアント呼び出しまでを一貫して生成できます。
// tRPC の型推論の仕組み(概念図)
// サーバー側で定義した input/output がクライアントに自動伝播
//
// Server: router.user.getById({ id: string }) => { name: string, email: string }
// ↓ TypeScript の型推論
// Client: trpc.user.getById.useQuery({ id: "123" })
// → 戻り値の型が { name: string, email: string } と自動推論されるAntigravity で tRPC プロジェクトをセットアップする
Antigravity の AI エージェントを使えば、tRPC プロジェクトの初期構築を大幅に効率化できます。まず、Next.js + tRPC のプロジェクトをエージェントに依頼して作成しましょう。
Antigravity のチャットパネルに以下のようなプロンプトを入力します。
Next.js 15 App Router + tRPC v11 + Zod のプロジェクトを作成してください。
以下の構成でお願いします:
- src/server/trpc.ts にルーター設定
- src/server/routers/ にルーター定義
- src/lib/trpc.ts にクライアント設定
- Zod でバリデーション
エージェントが生成する典型的なベースコードは以下のようになります。
// src/server/trpc.ts — tRPC の初期設定
import { initTRPC, TRPCError } from "@trpc/server";
import { ZodError } from "zod";
const t = initTRPC.create({
errorFormatter({ shape, error }) {
return {
...shape,
data: {
...shape.data,
zodError:
error.cause instanceof ZodError ? error.cause.flatten() : null,
},
};
},
});
// ベースとなるルーターとプロシージャ
export const router = t.router;
export const publicProcedure = t.procedure;// src/server/routers/user.ts — ユーザールーターの例
import { z } from "zod";
import { router, publicProcedure } from "../trpc";
export const userRouter = router({
getById: publicProcedure
.input(z.object({ id: z.string().uuid() }))
.query(async ({ input }) => {
// DB からユーザーを取得(例: Prisma)
const user = await db.user.findUnique({
where: { id: input.id },
});
if (!user) {
throw new TRPCError({
code: "NOT_FOUND",
message: "ユーザーが見つかりません",
});
}
return user;
// 期待する出力: { id: string, name: string, email: string, createdAt: Date }
}),
create: publicProcedure
.input(
z.object({
name: z.string().min(1).max(100),
email: z.string().email(),
})
)
.mutation(async ({ input }) => {
const newUser = await db.user.create({ data: input });
return newUser;
// 期待する出力: { id: string, name: string, email: string, createdAt: Date }
}),
});Zod バリデーションと tRPC の統合パターン
tRPC と Zod の組み合わせは非常に強力です。Antigravity にバリデーションスキーマを依頼する際は、再利用可能なスキーマとして定義すると保守性が向上します。
// src/shared/schemas/user.ts — 共有バリデーションスキーマ
import { z } from "zod";
// 基本スキーマ(サーバー・クライアント両方で使用)
export const createUserSchema = z.object({
name: z
.string()
.min(1, "名前は必須です")
.max(100, "名前は100文字以内で入力してください"),
email: z
.string()
.email("有効なメールアドレスを入力してください"),
role: z.enum(["admin", "user", "viewer"]).default("user"),
});
export const updateUserSchema = createUserSchema.partial().extend({
id: z.string().uuid(),
});
// 型を自動エクスポート(クライアントで使用)
export type CreateUserInput = z.infer<typeof createUserSchema>;
export type UpdateUserInput = z.infer<typeof updateUserSchema>;このスキーマを tRPC ルーターで参照すると、バリデーションと型安全性が同時に実現します。
// src/server/routers/user.ts での使用例
import { createUserSchema, updateUserSchema } from "@/shared/schemas/user";
export const userRouter = router({
create: publicProcedure
.input(createUserSchema) // Zod スキーマをそのまま渡す
.mutation(async ({ input }) => {
// input は CreateUserInput 型に自動推論
// input.name: string, input.email: string, input.role: "admin" | "user" | "viewer"
return await db.user.create({ data: input });
}),
update: publicProcedure
.input(updateUserSchema)
.mutation(async ({ input }) => {
// input.id は必須、他フィールドはオプショナル
const { id, ...data } = input;
return await db.user.update({ where: { id }, data });
}),
});Antigravity のエージェントにスキーマ生成を依頼する際は「Zod スキーマを共有ディレクトリに定義して、tRPC ルーターとフォームの両方で使えるようにして」と伝えると、最適な構成で出力されます。
React Query 統合によるクライアントサイド実装
tRPC v11 は React Query(TanStack Query)とシームレスに統合されています。Antigravity でクライアントコンポーネントを生成する際も、tRPC hooks を活用したコードが自動的に出力されます。
// src/lib/trpc.ts — クライアント設定
import { createTRPCReact } from "@trpc/react-query";
import type { AppRouter } from "@/server/routers/_app";
export const trpc = createTRPCReact<AppRouter>();// src/components/UserList.tsx — tRPC + React Query の実装例
"use client";
import { trpc } from "@/lib/trpc";
export function UserList() {
// useQuery: 型安全なデータフェッチ
const { data: users, isLoading, error } = trpc.user.list.useQuery(
{ limit: 20, offset: 0 },
{
staleTime: 5 * 60 * 1000, // 5分間キャッシュ
retry: 2,
}
);
// useMutation: 型安全なデータ更新
const utils = trpc.useUtils();
const createUser = trpc.user.create.useMutation({
onSuccess: () => {
// 成功時にユーザーリストを再取得
utils.user.list.invalidate();
},
onError: (err) => {
// Zod バリデーションエラーの表示
if (err.data?.zodError) {
const fieldErrors = err.data.zodError.fieldErrors;
console.error("バリデーションエラー:", fieldErrors);
}
},
});
if (isLoading) return <div>読み込み中...</div>;
if (error) return <div>エラー: {error.message}</div>;
return (
<div>
<ul>
{users?.map((user) => (
// user の型が自動推論される
<li key={user.id}>{user.name} ({user.email})</li>
))}
</ul>
<button
onClick={() =>
createUser.mutate({
name: "新しいユーザー",
email: "new@example.com",
})
}
>
ユーザーを追加
</button>
</div>
);
}
// 期待する出力: ユーザー一覧が表示され、ボタンクリックで新しいユーザーが追加されるエラーハンドリングとミドルウェアの実装
本番環境では認証やエラーハンドリングが不可欠です。tRPC のミドルウェア機構を使えば、これらを宣言的に実装できます。
// src/server/trpc.ts — 認証ミドルウェアの追加
import { initTRPC, TRPCError } from "@trpc/server";
import type { Context } from "./context";
const t = initTRPC.context<Context>().create();
// 認証チェックミドルウェア
const isAuthed = t.middleware(({ ctx, next }) => {
if (!ctx.session?.user) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "ログインが必要です",
});
}
return next({
ctx: {
session: ctx.session,
user: ctx.session.user, // 以降のプロシージャで user にアクセス可能
},
});
});
// レート制限ミドルウェア
const rateLimit = t.middleware(async ({ ctx, next, path }) => {
const key = `ratelimit:${ctx.ip}:${path}`;
const allowed = await checkRateLimit(key, { max: 100, window: 60 });
if (!allowed) {
throw new TRPCError({
code: "TOO_MANY_REQUESTS",
message: "リクエスト制限に達しました。しばらく待ってから再試行してください",
});
}
return next();
});
export const router = t.router;
export const publicProcedure = t.procedure;
export const protectedProcedure = t.procedure.use(isAuthed);
export const rateLimitedProcedure = t.procedure.use(rateLimit);Antigravity のエージェントに「認証付きの tRPC ルーターを作成して」と指示すると、このようなミドルウェア構成が自動的に含まれます。ミドルウェアの追加は .use() をチェインするだけなので、Antigravity が既存のルーターに認証を追加する際も最小限の変更で済みます。
Antigravity のエージェントを活用した効率的な開発フロー
tRPC 開発で Antigravity の AI エージェントが特に力を発揮する場面をまとめます。
ルーター定義の自動生成: データモデルの説明を伝えるだけで、CRUD ルーターが Zod スキーマ付きで生成されます。「Post モデルの tRPC ルーターを作成して。title, content, published フィールドがあり、一覧取得にはカーソルベースのページネーションを入れて」のように依頼しましょう。
エラーハンドリングの一括追加: 既存のルーターに対して「すべての query にエラーハンドリングを追加して、NOT_FOUND と INTERNAL_SERVER_ERROR を適切に処理するようにして」と指示できます。
テストコードの生成: tRPC ルーターのユニットテストも生成できます。「userRouter のすべてのプロシージャに対して Vitest のテストを書いて」と依頼すれば、モックデータ付きのテストファイルが出力されます。
Prisma や Drizzle ORM との連携についてさらに詳しく知りたい方は、Antigravity + Prisma ORM で型安全なデータベース操作やAntigravity + Drizzle ORMの記事も参考にしてください。
全体を振り返って — tRPC と Antigravity で型安全な開発体験を
tRPC は TypeScript フルスタック開発における型安全性の課題を根本から解決するライブラリです。Antigravity の AI エージェントと組み合わせることで、ルーター定義、バリデーションスキーマ、クライアントコンポーネントの生成を効率化でき、エンドツーエンドの型安全な API を短時間で構築できます。
まずは小さなプロジェクトで tRPC + Antigravity のワークフローを試してみてください。一度この型推論の快適さを体験すると、手動で API の型定義を書く作業には戻れなくなるはずです。