trpc

Deep Knowledge: Use mcp__documentation__fetch_docs with technology: trpc for comprehensive documentation.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "trpc" with this command: npx skills add claude-dev-suite/claude-dev-suite/claude-dev-suite-claude-dev-suite-trpc

tRPC Core Knowledge

Deep Knowledge: Use mcp__documentation__fetch_docs with technology: trpc for comprehensive documentation.

Router Definition

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

const t = initTRPC.context<Context>().create();

export const appRouter = t.router({ user: t.router({ list: t.procedure.query(async ({ ctx }) => { return ctx.db.users.findMany(); }),

byId: t.procedure
  .input(z.string())
  .query(async ({ ctx, input }) => {
    const user = await ctx.db.users.find(input);
    if (!user) throw new TRPCError({ code: 'NOT_FOUND' });
    return user;
  }),

create: t.procedure
  .input(z.object({
    name: z.string().min(1),
    email: z.string().email(),
  }))
  .mutation(async ({ ctx, input }) => {
    return ctx.db.users.create(input);
  }),

}), });

export type AppRouter = typeof appRouter;

Client Usage (React)

import { trpc } from '../utils/trpc';

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

if (isLoading) return <Spinner />;

return ( <div> {data?.map(user => <UserCard key={user.id} user={user} />)} <button onClick={() => createUser.mutate({ name: 'John', email: 'j@example.com' })}> Add User </button> </div> ); }

Protected Procedures

const protectedProcedure = t.procedure.use(async ({ ctx, next }) => { if (!ctx.session?.user) { throw new TRPCError({ code: 'UNAUTHORIZED' }); } return next({ ctx: { user: ctx.session.user } }); });

export const appRouter = t.router({ secret: protectedProcedure.query(({ ctx }) => { return Hello ${ctx.user.name}; }), });

With Next.js

// pages/api/trpc/[trpc].ts import { createNextApiHandler } from '@trpc/server/adapters/next'; import { appRouter } from '../../../server/routers/_app';

export default createNextApiHandler({ router: appRouter, createContext: ({ req, res }) => ({ req, res, db }), });

When NOT to Use This Skill

  • REST API design (use rest-api skill)

  • GraphQL APIs (use graphql skill)

  • OpenAPI documentation (use openapi skill)

  • Non-TypeScript projects

  • Public APIs requiring language-agnostic clients

  • APIs consumed by third-party developers

Anti-Patterns

Anti-Pattern Why It's Bad Solution

No input validation Security risk, runtime errors Always use Zod schemas for input

Sharing database models as output types Leaks implementation details Create separate DTOs/response schemas

Missing error handling middleware Inconsistent error responses Add global error middleware

No rate limiting on public procedures API abuse vulnerability Add rate limiting middleware

Using any in context or procedures Loses type safety Use proper TypeScript types

Not using middleware for auth Duplicated auth logic Create reusable protected procedure

Missing pagination on list queries Performance issues Add pagination to all list endpoints

Exposing internal errors to client Security leak Use error formatter to sanitize errors

No request logging Hard to debug issues Add logging middleware

Quick Troubleshooting

Issue Possible Cause Solution

Type errors in client Router type not exported/imported Export AppRouter type from server

"UNAUTHORIZED" errors Missing or invalid context Check createContext, verify token

Input validation fails Input doesn't match Zod schema Verify request payload matches schema

Slow queries Missing DataLoader or N+1 queries Add batching/caching in context

CORS errors Missing CORS configuration Configure CORS in adapter

Procedure not found Router not registered or typo Check router structure, verify path

"Cannot call procedure" Calling query as mutation or vice versa Use correct method (query/mutation)

Rate limit errors Too many requests Implement exponential backoff

Type inference not working Incorrect client setup Ensure client uses correct AppRouter type

Production Readiness

Error Handling

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

const t = initTRPC.context<Context>().create({ errorFormatter({ shape, error }) { return { ...shape, data: { ...shape.data, // Add custom error data zodError: error.code === 'BAD_REQUEST' && error.cause instanceof ZodError ? error.cause.flatten() : null, }, }; }, });

// Custom error handling middleware const errorMiddleware = t.middleware(async ({ next }) => { try { return await next(); } catch (error) { if (error instanceof TRPCError) { // Log known errors console.warn('tRPC error:', error.code, error.message); throw error; }

// Log and transform unknown errors
console.error('Unexpected error:', error);
throw new TRPCError({
  code: 'INTERNAL_SERVER_ERROR',
  message: 'An unexpected error occurred',
});

} });

export const procedure = t.procedure.use(errorMiddleware);

Rate Limiting

import { Ratelimit } from '@upstash/ratelimit'; import { Redis } from '@upstash/redis';

const ratelimit = new Ratelimit({ redis: Redis.fromEnv(), limiter: Ratelimit.slidingWindow(100, '1 m'), });

const rateLimitMiddleware = t.middleware(async ({ ctx, next }) => { const identifier = ctx.session?.user?.id ?? ctx.ip ?? 'anonymous'; const { success, limit, reset, remaining } = await ratelimit.limit(identifier);

if (!success) { throw new TRPCError({ code: 'TOO_MANY_REQUESTS', message: Rate limit exceeded. Try again in ${Math.ceil((reset - Date.now()) / 1000)}s, }); }

return next(); });

export const rateLimitedProcedure = t.procedure.use(rateLimitMiddleware);

Input Validation

import { z } from 'zod';

// Reusable schemas const paginationSchema = z.object({ page: z.number().min(1).default(1), limit: z.number().min(1).max(100).default(20), });

const idSchema = z.string().uuid();

const createUserSchema = z.object({ name: z.string().min(2).max(100), email: z.string().email(), role: z.enum(['user', 'admin']).default('user'), });

export const appRouter = t.router({ user: t.router({ list: procedure .input(paginationSchema) .query(async ({ ctx, input }) => { const { page, limit } = input; return ctx.db.users.findMany({ skip: (page - 1) * limit, take: limit, }); }),

create: protectedProcedure
  .input(createUserSchema)
  .mutation(async ({ ctx, input }) => {
    return ctx.db.users.create({ data: input });
  }),

}), });

Testing

// server/routers/tests/user.test.ts import { createCallerFactory } from '@trpc/server'; import { appRouter } from '../_app'; import { createMockContext } from '../../test/context';

const createCaller = createCallerFactory(appRouter);

describe('user router', () => { it('lists users', async () => { const ctx = createMockContext({ db: { users: { findMany: vi.fn().mockResolvedValue([{ id: '1', name: 'Test' }]), }, }, });

const caller = createCaller(ctx);
const result = await caller.user.list({ page: 1, limit: 10 });

expect(result).toHaveLength(1);
expect(ctx.db.users.findMany).toHaveBeenCalled();

});

it('throws on unauthorized access', async () => { const ctx = createMockContext({ session: null }); const caller = createCaller(ctx);

await expect(caller.user.create({ name: 'Test', email: 'test@example.com' }))
  .rejects.toThrow('UNAUTHORIZED');

}); });

// E2E testing import { test, expect } from '@playwright/test';

test('creates user via tRPC', async ({ page }) => { await page.goto('/users'); await page.fill('input[name="name"]', 'John'); await page.fill('input[name="email"]', 'john@example.com'); await page.click('button[type="submit"]');

await expect(page.locator('text=John')).toBeVisible(); });

Monitoring Metrics

Metric Target

Procedure latency < 200ms

Error rate < 1%

Rate limit hits Monitor

Query cache hit rate

80%

Checklist

  • Error formatter configured

  • Error logging middleware

  • Rate limiting on mutations

  • Input validation with Zod

  • Protected procedures for auth

  • Pagination on list queries

  • Unit tests with createCaller

  • E2E tests for critical flows

  • Request logging

  • Query invalidation strategy

Reference Documentation

  • Middleware

  • Error Handling

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

cron-scheduling

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

token-optimization

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

react-19

No summary provided by upstream source.

Repository SourceNeeds Review