tRPC 実践:エンドツーエンド型安全・Zod バリデーション・React Query 連携

前端工程

tRPC を使う価値

従来のフロント/バック協業は「手書き API ドキュメント + 別々に管理する型」に頼っており、型を変えるとすぐにズレが生じます。tRPC の核心は:バックエンドの procedure そのものが型の出所となり、フロントから呼ぶ際に完全な型推論を得られる。codegen も schema ファイルも不要

手法 型の出所 codegen 要否 跨言語
REST + 手書き型 手動管理 いいえ
GraphQL + codegen schema ファイル はい
tRPC バック手続きから直接推論 いいえ 不可(TS/TS のみ)

Router と Procedure の定義

// 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;

// server/routers/user.ts
export const userRouter = router({
  byId: publicProcedure
    .input(z.object({ id: z.string().uuid() }))
    .query(({ input }) => {
      return db.user.findUnique({ where: { id: input.id } });
    }),
  create: publicProcedure
    .input(z.object({ name: z.string().min(1), email: z.string().email() }))
    .mutation(({ input }) => {
      return db.user.create({ data: input });
    }),
});

query は読み取り、mutation は書き込みに対応。入力は Zod で宣言し、tRPC が実行時に自動検証します。

複雑な入れ子入力は、先に JSON フォーマッタ で形を整えてから Zod schema に変換すると、フィールド漏れを防げます。


React Query との統合

これが定番の組み合わせです。tRPC が型を、React Query がキャッシュ/リトライ/無効化を担います。

// client/trpc.ts
import { createTRPCReact } from "@trpc/react-query";
import type { AppRouter } from "../server/routers";

export const trpc = createTRPCReact<AppRouter>();

function UserList() {
  const users = trpc.user.list.useQuery();
  const utils = trpc.useUtils();
  const create = trpc.user.create.useMutation({
    onSuccess: () => utils.user.list.invalidate(),
  });

  if (users.isLoading) return <p>読み込み中…</p>;
  return (
    <ul>
      {users.data?.map((u) => (
        <li key={u.id}>{u.name}</li>
      ))}
      <button onClick={() => create.mutate({ name: "Ada", email: "a@x.com" })}>
        追加
      </button>
    </ul>
  );
}

users.data の型も create.mutate の引数型も、すべてバックエンド procedure から推論されます。バック入力を変えればフロントは即座にエラーになります。


ミドルウェアと認証

import { initTRPC, TRPCError } from "@trpc/server";

const t = initTRPC.context<{ jwt?: string }>().create();
const isAuthed = t.middleware(({ ctx, next }) => {
  if (!ctx.jwt) throw new TRPCError({ code: "UNAUTHORIZED" });
  return next({ ctx: { ...ctx, userId: verify(ctx.jwt) } });
});

export const protectedProcedure = t.procedure.use(isAuthed);

ミドルウェアは ctx を書き換えられ、下流 procedure は userId を受け取ります。

JWT 検証時は JWT デコード ツールで payload の sub と有効期限を確認し、認証チェーンを素早く照合できます。


エラー処理

tRPC は TRPCError でエラーを統一し、フロントは error.data.code で判別します:

protectedProcedure.query(({ ctx }) => {
  const u = db.user.findUnique({ where: { id: ctx.userId } });
  if (!u) throw new TRPCError({ code: "NOT_FOUND", message: "ユーザーが存在しません" });
  return u;
});

フロント:

const { error } = trpc.user.me.useQuery();
if (error?.data?.code === "NOT_FOUND") return <p>見つかりません</p>;

エラーコードは HTTP ステータスコードと 1:1 対応しており、調査時は HTTP ステータスコード ツールで UNAUTHORIZED=401NOT_FOUND=404 等を照合できます。


Subscriptions(リアルタイム)

tRPC は WebSocket ベースの購読をサポートします:

export const appRouter = router({
  onMessage: publicProcedure.subscription(({ signal }) => {
    return observable<Message>((emit) => {
      const timer = setInterval(() => emit.next({ text: "ping" }), 1000);
      return () => clearInterval(timer);
    });
  }),
});

REST からのスムーズな移行

  1. 境界を整理:「読み」→ query、「書き/副作用」→ mutation
  2. Zod を補完:各エンドポイントに入力 schema を付け、実行時検証を手に入れる。
  3. クライアントを置換fetch + 手書き型を trpc.xxx.useQuery/useMutation へ。
  4. 認証を下沉:散在する JWT チェックを protectedProcedure ミドルウェアに集約。
  5. REST を維持:tRPC は fetch adapter で HTTP エンドポイントも公開でき、段階的移行可能。

よくある質問 FAQ

Q1:tRPC は跨言語ですか?

いいえ。TypeScript の型推論に依存するため、両端とも TS である必要があります。多言語が必要なら REST/GraphQL に戻ります。

Q2:GraphQL と比べた優位性は?

schema ファイルと codegen が不要で、型がそのままソースコードになります。代償は跨言語や公開 API 能力の喪失です。

Q3:Zod 検証失敗時の戻りは?

自動的に BAD_REQUEST エラーになり、フロントは error.data.code === "BAD_REQUEST" とともに Zod 問題リストを受け取ります。

Q4:React Query を使わなくてもいいですか?

可能です。tRPC コアはクライアント非依存で、ネイティブ client 呼び出しもありますが、React Query 統合の DX が最良です。

Q5:大きな一覧のページネーションは?

inputcursor/limit を加え、query{ items, nextCursor } を返し、フロントは useInfiniteQuery と組み合わせます。


おすすめツール

tRPC フルスタック開発では、以下の ツール が役立ちます:


tRPC は「また一つの RPC フレームワーク」ではなく、「型こそ契約」というパラダイムです。バックエンドの procedure がそのままフロントの型の出所となり、ドキュメントや codegen、両側でのフィールド合わせという心的負荷をなくします。バック入力を変えればエディタがフロントを即座に赤くする——そのエンドツーエンドの型安全こそが、现代フルスタック TS チームのスイートスポットです。

ブラウザローカルツールを無料で試す →

#tRPC#类型安全#全栈#API#Zod#React Query#前端