trpc-fullstack – Coding skill for AI agents
Build end-to-end type-safe APIs with tRPC — routers, procedures, middleware, subscriptions, and Next.js/React integration patterns.
Install the trpc-fullstack skill with one command
Pick the tool you use, copy the command and paste it in your terminal. The skill lands in that tool's skills folder and is ready in your next session.
Claude Code
mkdir -p ~/.claude/skills && curl -fsSL https://skill.dn1nguoi.com/z/trpc-fullstack-9839c.tgz | tar xz -C ~/.claude/skills
Call it in Claude Code: /trpc-fullstack – then describe the task you want done.
Codex
mkdir -p ~/.codex/skills && curl -fsSL https://skill.dn1nguoi.com/z/trpc-fullstack-9839c.tgz | tar xz -C ~/.codex/skills
Call it in Codex: $trpc-fullstack – then describe the task you want done.
Hermes Agent
mkdir -p ~/.hermes/skills && curl -fsSL https://skill.dn1nguoi.com/z/trpc-fullstack-9839c.tgz | tar xz -C ~/.hermes/skills
Call it in Hermes Agent: /trpc-fullstack – then describe the task you want done.
DeepSeek
mkdir -p ~/.deepcode/skills && curl -fsSL https://skill.dn1nguoi.com/z/trpc-fullstack-9839c.tgz | tar xz -C ~/.deepcode/skills
Call it in DeepSeek: /trpc-fullstack – then describe the task you want done.
SKILL.md content
tRPC Full-Stack
Overview
tRPC lets you build fully type-safe APIs without writing a schema or code-generation step. Your TypeScript types flow from the server router directly to the client — so every API call is autocompleted, validated at compile time, and refactoring-safe. Use this skill when building TypeScript monorepos, Next.js apps, or any project where the server and client share a codebase.
When to Use This Skill
- Use when building a TypeScript full-stack app (Next.js, Remix, Express + React) where the client and server share a single repo
- Use when you want end-to-end type safety on API calls without REST/GraphQL schema overhead
- Use when adding real-time features (subscriptions) to an existing tRPC setup
- Use when designing multi-step middleware (auth, rate limiting, tenant scoping) on tRPC procedures
- Use when migrating an existing REST/GraphQL API to tRPC incrementally
Examples require matching tRPC/TanStack Query versions and application-specific auth, database models and user/session types. Cron jobs cannot assume request-bound auth() exists: build an explicitly authorized service context. The shown subscription router also needs root registration, a transport adapter and client link; its process-local emitter does not distribute across replicas. HTTP GET/POST handlers do not alone provide WebSockets. Type safety is not authorization: scope queries/output fields to permitted records, handle CSRF/origin for cookie-authenticated mutations, and enforce ownership atomically with writes.
Core Concepts
Routers and Procedures
A router groups related procedures (think: endpoints). Procedures are typed functions — query for reads, mutation for writes, subscription for real-time streams.
Input Validation with Zod
All procedure inputs are validated with Zod schemas. The validated, typed input is available in the procedure handler — no manual parsing.
Context
context is shared state passed to every procedure — auth session, database client, request headers, etc. It is built once per request in a context factory. Important: Next.js App Router and Pages Router require separate context factories because App Router handlers receive a fetch Request, not a Node.js NextApiRequest.
Middleware
Middleware chains run before a procedure. Use them for authentication, logging, and request enrichment. They can extend the context for downstream procedures.
---
How It Works
Step 1: Install and Initialize
npm install @trpc/server @trpc/client @trpc/react-query @tanstack/react-query zodCreate the tRPC instance and reusable builders:
// src/server/trpc.ts
import { initTRPC, TRPCError } from '@trpc/server';
import { type Context } from './context';
import { ZodError } from 'zod';
const t = initTRPC.context<Context>().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;
export const middleware = t.middleware;
export const createCallerFactory = t.createCallerFactory;Step 2: Define Two Context Factories
Next.js App Router handlers receive a fetch Request (not a Node.js NextApiRequest), so the context
must be built differently depending on the call site. Define one factory per surface:
// src/server/context.ts
import { type FetchCreateContextFnOptions } from '@trpc/server/adapters/fetch';
import { auth } from '@/server/auth'; // Next-Auth v5 / your auth helper
import { db } from './db';
/**
* Context for the HTTP handler (App Router Route Handler).
* `opts.req` is the fetch Request — auth is resolved server-side via `auth()`.
*/
export async function createTRPCContext(opts: FetchCreateContextFnOptions) {
const session = await auth(); // server-side auth — no req/res needed
return { session, db, headers: opts.req.headers };
}
/**
* Context for direct server-side callers (Server Components, RSC, cron jobs).
* No HTTP request is involved, so we call auth() directly from the server.
*/
export async function createServerContext() {
const session = await auth();
return { session, db, headers: new Headers() }; // matches Context; no HTTP request metadata
}
export type Context = Awaited<ReturnType<typeof createTRPCContext>>;Step 3: Build an Auth Middleware and Protected Procedure
// src/server/trpc.ts (continued)
const enforceAuth = middleware(({ ctx, next }) => {
if (!ctx.session?.user) {
throw new TRPCError({ code: 'UNAUTHORIZED' });
}
return next({
ctx: {
// Narrows type: session is non-null from here
session: { ...ctx.session, user: ctx.session.user },
},
});
});
export const protectedProcedure = t.procedure.use(enforceAuth);Step 4: Create Routers
// src/server/routers/post.ts
import { z } from 'zod';
import { router, publicProcedure, protectedProcedure } from '../trpc';
import { TRPCError } from '@trpc/server';
export const postRouter = router({
list: publicProcedure
.input(
z.object({
limit: z.number().int().min(1).max(100).default(20),
cursor: z.string().optional(),
})
)
.query(async ({ ctx, input }) => {
const posts = await ctx.db.post.findMany({
take: input.limit + 1,
cursor: input.cursor ? { id: input.cursor } : undefined,
orderBy: [{ createdAt: 'desc' }, { id: 'desc' }],
});
const nextCursor =
posts.length > input.limit ? posts.pop()!.id : undefined;
return { posts, nextCursor };
}),
byId: publicProcedure
.input(z.object({ id: z.string() }))
.query(async ({ ctx, input }) => {
const post = await ctx.db.post.findUnique({ where: { id: input.id } });
if (!post) throw new TRPCError({ code: 'NOT_FOUND' });
return post;
}),
create: protectedProcedure
.input(
z.object({
title: z.string().min(1).max(200),
body: z.string().min(1),
})
)
.mutation(async ({ ctx, input }) => {
return ctx.db.post.create({
data: { ...input, authorId: ctx.session.user.id },
});
}),
delete: protectedProcedure
.input(z.object({ id: z.string() }))
.mutation(async ({ ctx, input }) => {
const post = await ctx.db.post.findUnique({ where: { id: input.id } });
if (!post) throw new TRPCError({ code: 'NOT_FOUND' });
if (post.authorId !== ctx.session.user.id)
throw new TRPCError({ code: 'FORBIDDEN' });
return ctx.db.post.delete({ where: { id: input.id } });
}),
});Step 5: Compose the Root Router and Export Types
// src/server/root.ts
import { router } from './trpc';
import { postRouter } from './routers/post';
import { userRouter } from './routers/user';
export const appRouter = router({
post: postRouter,
user: userRouter,
});
// Export the type for the client — never import the appRouter itself on the client
export type AppRouter = typeof appRouter;Step 6: Mount the API Handler (Next.js App Router)
The App Router handler must use fetchRequestHandler and the fetch-based context factory.
createTRPCContext receives FetchCreateContextFnOptions (with a fetch Request), not
a Pages Router req/res pair.
// src/app/api/trpc/[trpc]/route.ts
import { fetchRequestHandler } from '@trpc/server/adapters/fetch';
import { type FetchCreateContextFnOptions } from '@trpc/server/adapters/fetch';
import { appRouter } from '@/server/root';
import { createTRPCContext } from '@/server/context';
const handler = (req: Request) =>
fetchRequestHandler({
endpoint: '/api/trpc',
req,
router: appRouter,
// opts is FetchCreateContextFnOptions — req is the fetch Request
createContext: (opts: FetchCreateContextFnOptions) => createTRPCContext(opts),
});
export { handler as GET, handler as POST };Step 7: Set Up the Client (React Query)
// src/utils/trpc.ts
import { createTRPCReact } from '@trpc/react-query';
import type { AppRouter } from '@/server/root';
export const trpc = createTRPCReact<AppRouter>();// src/app/providers.tsx
'use client';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { httpBatchLink } from '@trpc/client';
import { useState } from 'react';
import { trpc } from '@/utils/trpc';
export function TRPCProvider({ children }: { children: React.ReactNode }) {
const [queryClient] = useState(() => new QueryClient());
const [trpcClient] = useState(() =>
trpc.createClient({
links: [
httpBatchLink({
url: '/api/trpc',
headers: () => ({ 'x-trpc-source': 'react' }),
}),
],
})
);
return (
<trpc.Provider client={trpcClient} queryClient={queryClient}>
… (full content is in the download package)
Related skills
- twilio-communications – Build communication features with Twilio: SMS messaging, voice calls, WhatsApp Business…
- typescript – Language-specific super-code guidelines for typescript.
- typescript-advanced-types – Comprehensive guidance for mastering TypeScript's advanced type system including…
- typescript-expert – TypeScript and JavaScript expert with deep knowledge of type-level programming…
- typescript-pro – Master TypeScript with advanced types, generics, and strict type safety. Handles complex…
- uncle-bob-craft – Use when performing code review, writing or refactoring code, or discussing…
- understand-project-yylo – Inspect the current product architecture, dependencies, and validation loops before…
- unit-testing-test-generate – Generate comprehensive, maintainable unit tests across languages with strong coverage…