スキル一覧に戻る
190km

trpc

by 190km

skill for tRPC

0🍴 0📅 2026年1月21日
GitHubで見るManusで実行

SKILL.md


name: trpc description: "End-to-end typesafe API development with tRPC v11. Use when building TypeScript APIs with type inference between client and server, creating routers and procedures, setting up React/Next.js integrations, implementing middleware and context, handling errors, or configuring real-time subscriptions. Triggers on tRPC setup, router creation, procedure definition, input validation with Zod, mutation/query implementation, React Query integration, WebSocket/SSE subscriptions."

tRPC Development Skill

Build end-to-end typesafe APIs with tRPC v11. This skill covers server setup, client integration, React Query, and real-time subscriptions.

Quick Reference

Core Concepts

  • Router: Container for procedures, defines API structure
  • Procedure: API endpoint (query, mutation, or subscription)
  • Context: Shared data available to all procedures (auth, db, etc.)
  • Middleware: Reusable logic that wraps procedures
  • Link: Controls data flow on the client (batching, retries, etc.)

Installation

# Server + Vanilla Client
npm install @trpc/server @trpc/client zod

# With React Query (recommended)
npm install @trpc/server @trpc/client @trpc/tanstack-react-query @tanstack/react-query zod

# With Next.js
npm install @trpc/server @trpc/client @trpc/tanstack-react-query @trpc/next @tanstack/react-query zod

Server Setup

1. Initialize tRPC (server/trpc.ts)

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

// Define context type
type Context = {
  db: PrismaClient;
  session: Session | null;
};

// Initialize tRPC - do this ONCE per backend
const t = initTRPC.context<Context>().create({
  transformer: superjson, // Preserves Date, Map, Set, etc.
  errorFormatter: ({ shape, error }) => ({
    ...shape,
    data: {
      ...shape.data,
      zodError: error.cause instanceof ZodError ? error.cause.flatten() : null,
    },
  }),
});

// Export reusable helpers
export const router = t.router;
export const publicProcedure = t.procedure;
export const middleware = t.middleware;
export const createCallerFactory = t.createCallerFactory;

2. Create Protected Procedure

const isAuthed = middleware(({ ctx, next }) => {
  if (!ctx.session?.user) {
    throw new TRPCError({ code: 'UNAUTHORIZED' });
  }
  return next({
    ctx: { user: ctx.session.user }, // Add user to context
  });
});

export const protectedProcedure = publicProcedure.use(isAuthed);

3. Define Router (server/routers/user.ts)

import { z } from 'zod';
import { router, publicProcedure, protectedProcedure } from '../trpc';

export const userRouter = router({
  // Query - for fetching data
  list: publicProcedure.query(async ({ ctx }) => {
    return ctx.db.user.findMany();
  }),

  // Query with input
  byId: publicProcedure
    .input(z.object({ id: z.string() }))
    .query(async ({ ctx, input }) => {
      const user = await ctx.db.user.findUnique({ where: { id: input.id } });
      if (!user) throw new TRPCError({ code: 'NOT_FOUND' });
      return user;
    }),

  // Mutation - for creating/updating/deleting
  create: protectedProcedure
    .input(z.object({
      name: z.string().min(1),
      email: z.string().email(),
    }))
    .mutation(async ({ ctx, input }) => {
      return ctx.db.user.create({ data: input });
    }),

  // Output validation (optional but recommended)
  getProfile: protectedProcedure
    .output(z.object({
      id: z.string(),
      name: z.string(),
      email: z.string(),
    }))
    .query(async ({ ctx }) => {
      return ctx.db.user.findUnique({ where: { id: ctx.user.id } });
    }),
});

4. Merge Routers (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;

5. Create Context

import { CreateNextContextOptions } from '@trpc/server/adapters/next';
import { getServerSession } from 'next-auth';

export const createContext = async (opts: CreateNextContextOptions) => {
  const session = await getServerSession(opts.req, opts.res, authOptions);
  return {
    db: prisma,
    session,
  };
};

export type Context = Awaited<ReturnType<typeof createContext>>;

Client Setup

Vanilla Client

import { createTRPCClient, httpBatchLink } from '@trpc/client';
import type { AppRouter } from './server/routers/_app';
import superjson from 'superjson';

const trpc = createTRPCClient<AppRouter>({
  links: [
    httpBatchLink({
      url: 'http://localhost:3000/api/trpc',
      transformer: superjson,
      headers: () => ({
        authorization: `Bearer ${getToken()}`,
      }),
    }),
  ],
});

// Usage
const users = await trpc.user.list.query();
const user = await trpc.user.byId.query({ id: '1' });
const newUser = await trpc.user.create.mutate({ name: 'John', email: 'john@example.com' });

See references/react-integration.md for complete setup.

// Quick example
const userQuery = useQuery(trpc.user.byId.queryOptions({ id: '1' }));
const createUser = useMutation(trpc.user.create.mutationOptions());

Middleware Patterns

// Logging middleware
const loggerMiddleware = middleware(async ({ path, type, next }) => {
  const start = Date.now();
  const result = await next();
  console.log(`${type} ${path} - ${Date.now() - start}ms`);
  return result;
});

// Rate limiting middleware
const rateLimitMiddleware = middleware(async ({ ctx, next }) => {
  const { success } = await ratelimit.limit(ctx.user.id);
  if (!success) throw new TRPCError({ code: 'TOO_MANY_REQUESTS' });
  return next();
});

// Chain middleware
export const rateLimitedProcedure = protectedProcedure
  .use(loggerMiddleware)
  .use(rateLimitMiddleware);

Error Handling

import { TRPCError } from '@trpc/server';

// Throw errors in procedures
throw new TRPCError({
  code: 'NOT_FOUND',        // HTTP 404
  message: 'User not found',
  cause: originalError,     // Optional
});

// Error codes: PARSE_ERROR, BAD_REQUEST, UNAUTHORIZED, FORBIDDEN,
// NOT_FOUND, METHOD_NOT_SUPPORTED, TIMEOUT, CONFLICT, PRECONDITION_FAILED,
// PAYLOAD_TOO_LARGE, UNPROCESSABLE_CONTENT, TOO_MANY_REQUESTS,
// CLIENT_CLOSED_REQUEST, INTERNAL_SERVER_ERROR

Additional Resources

Common Patterns

Input Chaining

const baseInput = z.object({ organizationId: z.string() });

const orgProcedure = protectedProcedure
  .input(baseInput)
  .use(async ({ ctx, input, next }) => {
    const org = await ctx.db.org.findUnique({ where: { id: input.organizationId } });
    if (!org) throw new TRPCError({ code: 'NOT_FOUND' });
    return next({ ctx: { org } });
  });

// Usage: automatically validates organizationId and adds org to context
export const orgRouter = router({
  getMembers: orgProcedure
    .input(z.object({ role: z.enum(['admin', 'member']).optional() }))
    .query(({ ctx, input }) => {
      // input.organizationId and input.role both available
      // ctx.org is typed and available
    }),
});

Optimistic Updates Pattern

// Client-side with React Query
const utils = trpc.useUtils();

const createPost = useMutation(
  trpc.post.create.mutationOptions({
    onMutate: async (newPost) => {
      await utils.post.list.cancel();
      const previous = utils.post.list.getData();
      utils.post.list.setData(undefined, (old) => [...(old ?? []), { ...newPost, id: 'temp' }]);
      return { previous };
    },
    onError: (err, newPost, context) => {
      utils.post.list.setData(undefined, context?.previous);
    },
    onSettled: () => {
      utils.post.list.invalidate();
    },
  })
);

スコア

総合スコア

35/100

リポジトリの品質指標に基づく評価

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

0/10
説明文

100文字以上の説明がある

0/10
人気

GitHub Stars 100以上

0/15
最近の活動

3ヶ月以内に更新がある

0/10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

0/5
タグ

1つ以上のタグが設定されている

0/5

レビュー

💬

レビュー機能は近日公開予定です