Agent Skills: trpc

>

UncategorizedID: poletron/custom-rules/trpc

Install this agent skill to your local

pnpm dlx add-skill https://github.com/odjaramillo/custom-rules/tree/HEAD/registry/skills/trpc

Skill Files

Browse the full folder contents for trpc.

Download Skill

Loading file tree…

registry/skills/trpc/SKILL.md

Skill Metadata

Name
trpc
Description
>

Critical Patterns

Project Structure (REQUIRED)

.
├── src
│   ├── pages
│   │   ├── _app.tsx  # add `createTRPCNext` setup here
│   │   ├── api
│   │   │   └── trpc
│   │   │       └── [trpc].ts  # tRPC HTTP handler
│   │   ├── server
│   │   │   ├── routers
│   │   │   │   ├── _app.ts  # main app router
│   │   │   │   ├── [feature].ts  # feature-specific routers
│   │   │   │   └── [...]
│   │   │   ├── context.ts   # create app context
│   │   │   └── trpc.ts      # procedure helpers
│   │   └── utils
│   │       └── trpc.ts  # typesafe tRPC hooks

Server-Side Setup (REQUIRED)

// server/trpc.ts - Initialize backend (once per backend)
import { initTRPC } from '@trpc/server';

const t = initTRPC.create();

export const router = t.router;
export const publicProcedure = t.procedure;

Router Definition (REQUIRED)

// server/routers/_app.ts
import { z } from 'zod';
import { router, publicProcedure } from '../trpc';

export const appRouter = router({
  greeting: publicProcedure.input(z.object({ name: z.string() })).query(({ input }) => {
    return `Hello ${input.name}`;
  }),
});

// Export type definition, NOT the router itself!
export type AppRouter = typeof appRouter;

Client-Side Setup (REQUIRED)

// utils/trpc.ts
import { httpBatchLink } from '@trpc/client';
import { createTRPCNext } from '@trpc/next';
import type { AppRouter } from '../server/routers/_app';

function getBaseUrl() {
  if (typeof window !== 'undefined') return '';
  if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`;
  return `http://localhost:${process.env.PORT ?? 3000}`;
}

export const trpc = createTRPCNext<AppRouter>({
  config() {
    return {
      links: [
        httpBatchLink({
          url: `${getBaseUrl()}/api/trpc`,
        }),
      ],
    };
  },
  ssr: false,
});

Decision Tree

Need public endpoint?      → Use publicProcedure
Need auth?                 → Use protectedProcedure with middleware
Need validation?           → Use Zod in .input()
Need caching?              → Use React Query options
Need complex types?        → Use SuperJSON transformer

Code Examples

Organize Routers by Feature

// server/routers/user.ts
export const userRouter = router({
  list: publicProcedure.query(() => {
    /* ... */
  }),
  byId: publicProcedure.input(z.string()).query(({ input }) => {
    /* ... */
  }),
  create: publicProcedure.input(/* ... */).mutation(({ input }) => {
    /* ... */
  }),
});

// server/routers/_app.ts
import { userRouter } from './user';
import { postRouter } from './post';

export const appRouter = router({
  user: userRouter,
  post: postRouter,
});

Middleware for Auth

const isAuthed = t.middleware(({ next, ctx }) => {
  if (!ctx.user) {
    throw new TRPCError({ code: 'UNAUTHORIZED' });
  }
  return next({ ctx: { user: ctx.user } });
});

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

Error Handling

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

publicProcedure.input(z.string()).query(({ input }) => {
  const user = getUserById(input);
  if (!user) {
    throw new TRPCError({
      code: 'NOT_FOUND',
      message: `User with id ${input} not found`,
    });
  }
  return user;
});

Data Transformers (SuperJSON)

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

const t = initTRPC.create({
  transformer: superjson,
});

React Query Integration

function UserProfile({ userId }: { userId: string }) {
  const { data, isLoading, error } = trpc.user.byId.useQuery(userId);

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return <div>{data.name}</div>;
}

Context Creation

// server/context.ts
import { inferAsyncReturnType } from '@trpc/server';
import * as trpcNext from '@trpc/server/adapters/next';

export async function createContext({ req, res }: trpcNext.CreateNextContextOptions) {
  const user = await getUser(req);
  return { req, res, prisma, user };
}

export type Context = inferAsyncReturnType<typeof createContext>;

Procedure Types

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

Performance: Batching & Prefetching

// Client batching
httpBatchLink({
  url: `${getBaseUrl()}/api/trpc`,
  maxURLLength: 2083,
});

// Prefetching in Next.js
export async function getStaticProps() {
  const ssg = createServerSideHelpers({
    router: appRouter,
    ctx: {},
  });

  await ssg.post.byId.prefetch('1');

  return {
    props: { trpcState: ssg.dehydrate() },
    revalidate: 1,
  };
}

Version Compatibility

  • tRPC v11
  • TypeScript >= 5.7.2
  • Strict mode required ("strict": true)