tRPC โ€” community dmbk-world, community, ide skills, Claude Code, Cursor, Windsurf

v1.0.0
GitHub

About this Skill

Ideal for TypeScript-focused AI Agents seeking end-to-end typesafe APIs without schemas or code generation. ๐‘€๐‘œ๐’พ ๐ผ๐“‚๐’ถ๐‘”๐’พ๐“ƒ๐’ถ๐“‰๐’พ๐‘œ๐“ƒ๐ฟ๐’ถ๐“ƒ๐’น

dblodorn dblodorn
[0]
[0]
Updated: 3/5/2026

Agent Capability Analysis

The tRPC skill by dblodorn is an open-source community AI agent skill for Claude Code and other IDE workflows, helping agents execute tasks with better context, repeatability, and domain-specific guidance.

Ideal Agent Persona

Ideal for TypeScript-focused AI Agents seeking end-to-end typesafe APIs without schemas or code generation.

Core Value

Empowers agents to build fully typesafe APIs with excellent developer experience, utilizing TypeScript inference from server to client, and seamless integration with Next.js and React Query.

โ†“ Capabilities Granted for tRPC

Building typesafe APIs with automatic TypeScript inference
Integrating with Next.js for robust frontend-backend interactions
Enhancing API development with autocomplete and type safety features

! Prerequisites & Limits

  • Requires TypeScript setup and configuration
  • Compatibility limited to Next.js, React Query, and similar frameworks
Labs Demo

Browser Sandbox Environment

โšก๏ธ Ready to unleash?

Experience this Agent in a zero-setup browser environment powered by WebContainers. No installation required.

Boot Container Sandbox

tRPC

Install tRPC, an AI agent skill for AI agent workflows and automation. Works with Claude Code, Cursor, and Windsurf with one-command setup.

SKILL.md
Readonly

tRPC

Expert assistance with tRPC - End-to-end typesafe APIs with TypeScript.

Overview

tRPC enables building fully typesafe APIs without schemas or code generation:

  • Full TypeScript inference from server to client
  • No code generation needed
  • Excellent DX with autocomplete and type safety
  • Works great with Next.js, React Query, and more

Quick Start

Installation

bash
1# Core packages 2npm install @trpc/server@next @trpc/client@next @trpc/react-query@next 3 4# Peer dependencies 5npm install @tanstack/react-query@latest zod

Basic Setup (Next.js App Router)

1. Create tRPC Router

typescript
1// server/trpc.ts 2import { initTRPC } from '@trpc/server' 3import { z } from 'zod' 4 5const t = initTRPC.create() 6 7export const router = t.router 8export const publicProcedure = t.procedure

2. Define API Router

typescript
1// server/routers/_app.ts 2import { router, publicProcedure } from '../trpc' 3import { z } from 'zod' 4 5export const appRouter = router({ 6 hello: publicProcedure 7 .input(z.object({ name: z.string() })) 8 .query(({ input }) => { 9 return { greeting: `Hello ${input.name}!` } 10 }), 11 12 createUser: publicProcedure 13 .input(z.object({ 14 name: z.string(), 15 email: z.string().email(), 16 })) 17 .mutation(async ({ input }) => { 18 const user = await db.user.create({ data: input }) 19 return user 20 }), 21}) 22 23export type AppRouter = typeof appRouter

3. Create API Route

typescript
1// app/api/trpc/[trpc]/route.ts 2import { fetchRequestHandler } from '@trpc/server/adapters/fetch' 3import { appRouter } from '@/server/routers/_app' 4 5const handler = (req: Request) => 6 fetchRequestHandler({ 7 endpoint: '/api/trpc', 8 req, 9 router: appRouter, 10 createContext: () => ({}), 11 }) 12 13export { handler as GET, handler as POST }

4. Setup Client Provider

typescript
1// app/providers.tsx 2'use client' 3 4import { QueryClient, QueryClientProvider } from '@tanstack/react-query' 5import { httpBatchLink } from '@trpc/client' 6import { useState } from 'react' 7import { trpc } from '@/lib/trpc' 8 9export function Providers({ children }: { children: React.ReactNode }) { 10 const [queryClient] = useState(() => new QueryClient()) 11 const [trpcClient] = useState(() => 12 trpc.createClient({ 13 links: [ 14 httpBatchLink({ 15 url: 'http://localhost:3000/api/trpc', 16 }), 17 ], 18 }) 19 ) 20 21 return ( 22 <trpc.Provider client={trpcClient} queryClient={queryClient}> 23 <QueryClientProvider client={queryClient}> 24 {children} 25 </QueryClientProvider> 26 </trpc.Provider> 27 ) 28}

5. Create tRPC Client

typescript
1// lib/trpc.ts 2import { createTRPCReact } from '@trpc/react-query' 3import type { AppRouter } from '@/server/routers/_app' 4 5export const trpc = createTRPCReact<AppRouter>()

6. Use in Components

typescript
1'use client' 2 3import { trpc } from '@/lib/trpc' 4 5export default function Home() { 6 const hello = trpc.hello.useQuery({ name: 'World' }) 7 const createUser = trpc.createUser.useMutation() 8 9 return ( 10 <div> 11 <p>{hello.data?.greeting}</p> 12 <button 13 onClick={() => createUser.mutate({ 14 name: 'John', 15 email: 'john@example.com' 16 })} 17 > 18 Create User 19 </button> 20 </div> 21 ) 22}

Router Definition

Basic Router

typescript
1import { router, publicProcedure } from './trpc' 2import { z } from 'zod' 3 4export const userRouter = router({ 5 // Query - for fetching data 6 getById: publicProcedure 7 .input(z.string()) 8 .query(async ({ input }) => { 9 return await db.user.findUnique({ where: { id: input } }) 10 }), 11 12 // Mutation - for creating/updating/deleting 13 create: publicProcedure 14 .input(z.object({ 15 name: z.string(), 16 email: z.string().email(), 17 })) 18 .mutation(async ({ input }) => { 19 return await db.user.create({ data: input }) 20 }), 21 22 // Subscription - for real-time updates 23 onUpdate: publicProcedure 24 .subscription(() => { 25 return observable<User>((emit) => { 26 // Implementation 27 }) 28 }), 29})

Nested Routers

typescript
1import { router } from './trpc' 2import { userRouter } from './routers/user' 3import { postRouter } from './routers/post' 4import { commentRouter } from './routers/comment' 5 6export const appRouter = router({ 7 user: userRouter, 8 post: postRouter, 9 comment: commentRouter, 10}) 11 12// Usage on client: 13// trpc.user.getById.useQuery('123') 14// trpc.post.list.useQuery() 15// trpc.comment.create.useMutation()

Merging Routers

typescript
1import { router, publicProcedure } from './trpc' 2 3const userRouter = router({ 4 list: publicProcedure.query(() => {/* ... */}), 5 getById: publicProcedure.input(z.string()).query(() => {/* ... */}), 6}) 7 8const postRouter = router({ 9 list: publicProcedure.query(() => {/* ... */}), 10 create: publicProcedure.input(z.object({})).mutation(() => {/* ... */}), 11}) 12 13// Merge into app router 14export const appRouter = router({ 15 user: userRouter, 16 post: postRouter, 17})

Input Validation with Zod

Basic Validation

typescript
1import { z } from 'zod' 2 3export const userRouter = router({ 4 create: publicProcedure 5 .input(z.object({ 6 name: z.string().min(2).max(50), 7 email: z.string().email(), 8 age: z.number().int().positive().optional(), 9 role: z.enum(['user', 'admin']), 10 })) 11 .mutation(async ({ input }) => { 12 // input is fully typed! 13 return await db.user.create({ data: input }) 14 }), 15})

Complex Validation

typescript
1const createPostInput = z.object({ 2 title: z.string().min(5).max(100), 3 content: z.string().min(10), 4 published: z.boolean().default(false), 5 tags: z.array(z.string()).min(1).max(5), 6 metadata: z.object({ 7 views: z.number().default(0), 8 likes: z.number().default(0), 9 }).optional(), 10}) 11 12export const postRouter = router({ 13 create: publicProcedure 14 .input(createPostInput) 15 .mutation(async ({ input }) => { 16 return await db.post.create({ data: input }) 17 }), 18})

Reusable Schemas

typescript
1// schemas/user.ts 2export const userSchema = z.object({ 3 id: z.string(), 4 name: z.string(), 5 email: z.string().email(), 6}) 7 8export const createUserSchema = userSchema.omit({ id: true }) 9export const updateUserSchema = userSchema.partial() 10 11// Use in router 12export const userRouter = router({ 13 create: publicProcedure 14 .input(createUserSchema) 15 .mutation(({ input }) => {/* ... */}), 16 17 update: publicProcedure 18 .input(z.object({ 19 id: z.string(), 20 data: updateUserSchema, 21 })) 22 .mutation(({ input }) => {/* ... */}), 23})

Context

Creating Context

typescript
1// server/context.ts 2import { inferAsyncReturnType } from '@trpc/server' 3import { FetchCreateContextFnOptions } from '@trpc/server/adapters/fetch' 4 5export async function createContext(opts: FetchCreateContextFnOptions) { 6 // Get session from cookies/headers 7 const session = await getSession(opts.req) 8 9 return { 10 session, 11 db, 12 } 13} 14 15export type Context = inferAsyncReturnType<typeof createContext>

Using Context in tRPC

typescript
1// server/trpc.ts 2import { initTRPC } from '@trpc/server' 3import { Context } from './context' 4 5const t = initTRPC.context<Context>().create() 6 7export const router = t.router 8export const publicProcedure = t.procedure

Accessing Context in Procedures

typescript
1export const userRouter = router({ 2 me: publicProcedure.query(({ ctx }) => { 3 // ctx.session, ctx.db are available 4 if (!ctx.session) { 5 throw new TRPCError({ code: 'UNAUTHORIZED' }) 6 } 7 8 return ctx.db.user.findUnique({ 9 where: { id: ctx.session.userId } 10 }) 11 }), 12})

Middleware

Creating Middleware

typescript
1// server/trpc.ts 2import { initTRPC, TRPCError } from '@trpc/server' 3 4const t = initTRPC.context<Context>().create() 5 6// Logging middleware 7const loggerMiddleware = t.middleware(async ({ path, type, next }) => { 8 const start = Date.now() 9 const result = await next() 10 const duration = Date.now() - start 11 12 console.log(`${type} ${path} took ${duration}ms`) 13 14 return result 15}) 16 17// Auth middleware 18const isAuthed = t.middleware(({ ctx, next }) => { 19 if (!ctx.session) { 20 throw new TRPCError({ code: 'UNAUTHORIZED' }) 21 } 22 23 return next({ 24 ctx: { 25 // Infers session is non-nullable 26 session: ctx.session, 27 }, 28 }) 29}) 30 31// Create procedures with middleware 32export const publicProcedure = t.procedure.use(loggerMiddleware) 33export const protectedProcedure = t.procedure.use(loggerMiddleware).use(isAuthed)

Using Protected Procedures

typescript
1export const postRouter = router({ 2 // Public - anyone can access 3 list: publicProcedure.query(() => { 4 return db.post.findMany({ where: { published: true } }) 5 }), 6 7 // Protected - requires authentication 8 create: protectedProcedure 9 .input(z.object({ title: z.string() })) 10 .mutation(({ ctx, input }) => { 11 // ctx.session is guaranteed to exist 12 return db.post.create({ 13 data: { 14 ...input, 15 authorId: ctx.session.userId, 16 }, 17 }) 18 }), 19})

Role-Based Middleware

typescript
1const requireRole = (role: string) => 2 t.middleware(({ ctx, next }) => { 3 if (!ctx.session || ctx.session.role !== role) { 4 throw new TRPCError({ code: 'FORBIDDEN' }) 5 } 6 return next() 7 }) 8 9export const adminProcedure = protectedProcedure.use(requireRole('admin')) 10 11export const userRouter = router({ 12 delete: adminProcedure 13 .input(z.string()) 14 .mutation(({ input }) => { 15 return db.user.delete({ where: { id: input } }) 16 }), 17})

Client Usage

Queries

typescript
1'use client' 2 3import { trpc } from '@/lib/trpc' 4 5export default function UserList() { 6 // Basic query 7 const users = trpc.user.list.useQuery() 8 9 // Query with input 10 const user = trpc.user.getById.useQuery('user-123') 11 12 // Disabled query 13 const profile = trpc.user.getProfile.useQuery( 14 { id: userId }, 15 { enabled: !!userId } 16 ) 17 18 // With options 19 const posts = trpc.post.list.useQuery(undefined, { 20 refetchInterval: 5000, 21 staleTime: 1000, 22 }) 23 24 if (users.isLoading) return <div>Loading...</div> 25 if (users.error) return <div>Error: {users.error.message}</div> 26 27 return ( 28 <ul> 29 {users.data?.map(user => ( 30 <li key={user.id}>{user.name}</li> 31 ))} 32 </ul> 33 ) 34}

Mutations

typescript
1'use client' 2 3export default function CreateUser() { 4 const utils = trpc.useContext() 5 6 const createUser = trpc.user.create.useMutation({ 7 onSuccess: () => { 8 // Invalidate and refetch 9 utils.user.list.invalidate() 10 }, 11 onError: (error) => { 12 console.error('Failed to create user:', error) 13 }, 14 }) 15 16 const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { 17 e.preventDefault() 18 const formData = new FormData(e.currentTarget) 19 20 createUser.mutate({ 21 name: formData.get('name') as string, 22 email: formData.get('email') as string, 23 }) 24 } 25 26 return ( 27 <form onSubmit={handleSubmit}> 28 <input name="name" required /> 29 <input name="email" type="email" required /> 30 <button type="submit" disabled={createUser.isLoading}> 31 {createUser.isLoading ? 'Creating...' : 'Create User'} 32 </button> 33 </form> 34 ) 35}

Optimistic Updates

typescript
1const updatePost = trpc.post.update.useMutation({ 2 onMutate: async (newPost) => { 3 // Cancel outgoing refetches 4 await utils.post.list.cancel() 5 6 // Snapshot previous value 7 const previousPosts = utils.post.list.getData() 8 9 // Optimistically update 10 utils.post.list.setData(undefined, (old) => 11 old?.map(post => 12 post.id === newPost.id ? { ...post, ...newPost } : post 13 ) 14 ) 15 16 return { previousPosts } 17 }, 18 onError: (err, newPost, context) => { 19 // Rollback on error 20 utils.post.list.setData(undefined, context?.previousPosts) 21 }, 22 onSettled: () => { 23 // Refetch after success or error 24 utils.post.list.invalidate() 25 }, 26})

Infinite Queries

typescript
1// Server 2export const postRouter = router({ 3 list: publicProcedure 4 .input(z.object({ 5 cursor: z.string().optional(), 6 limit: z.number().min(1).max(100).default(10), 7 })) 8 .query(async ({ input }) => { 9 const posts = await db.post.findMany({ 10 take: input.limit + 1, 11 cursor: input.cursor ? { id: input.cursor } : undefined, 12 }) 13 14 let nextCursor: string | undefined = undefined 15 if (posts.length > input.limit) { 16 const nextItem = posts.pop() 17 nextCursor = nextItem!.id 18 } 19 20 return { posts, nextCursor } 21 }), 22}) 23 24// Client 25export default function InfinitePosts() { 26 const posts = trpc.post.list.useInfiniteQuery( 27 { limit: 10 }, 28 { 29 getNextPageParam: (lastPage) => lastPage.nextCursor, 30 } 31 ) 32 33 return ( 34 <div> 35 {posts.data?.pages.map((page, i) => ( 36 <div key={i}> 37 {page.posts.map(post => ( 38 <div key={post.id}>{post.title}</div> 39 ))} 40 </div> 41 ))} 42 43 <button 44 onClick={() => posts.fetchNextPage()} 45 disabled={!posts.hasNextPage || posts.isFetchingNextPage} 46 > 47 {posts.isFetchingNextPage ? 'Loading...' : 'Load More'} 48 </button> 49 </div> 50 ) 51}

Error Handling

Server Errors

typescript
1import { TRPCError } from '@trpc/server' 2 3export const postRouter = router({ 4 getById: publicProcedure 5 .input(z.string()) 6 .query(async ({ input }) => { 7 const post = await db.post.findUnique({ where: { id: input } }) 8 9 if (!post) { 10 throw new TRPCError({ 11 code: 'NOT_FOUND', 12 message: 'Post not found', 13 }) 14 } 15 16 return post 17 }), 18 19 create: protectedProcedure 20 .input(z.object({ title: z.string() })) 21 .mutation(async ({ ctx, input }) => { 22 if (!ctx.session.verified) { 23 throw new TRPCError({ 24 code: 'FORBIDDEN', 25 message: 'Email must be verified', 26 }) 27 } 28 29 try { 30 return await db.post.create({ data: input }) 31 } catch (error) { 32 throw new TRPCError({ 33 code: 'INTERNAL_SERVER_ERROR', 34 message: 'Failed to create post', 35 cause: error, 36 }) 37 } 38 }), 39})

Error Codes

  • BAD_REQUEST - Invalid input
  • UNAUTHORIZED - Not authenticated
  • FORBIDDEN - Not authorized
  • NOT_FOUND - Resource not found
  • TIMEOUT - Request timeout
  • CONFLICT - Resource conflict
  • PRECONDITION_FAILED - Precondition check failed
  • PAYLOAD_TOO_LARGE - Request too large
  • METHOD_NOT_SUPPORTED - HTTP method not supported
  • TOO_MANY_REQUESTS - Rate limited
  • CLIENT_CLOSED_REQUEST - Client closed request
  • INTERNAL_SERVER_ERROR - Server error

Client Error Handling

typescript
1const createPost = trpc.post.create.useMutation({ 2 onError: (error) => { 3 if (error.data?.code === 'UNAUTHORIZED') { 4 router.push('/login') 5 } else if (error.data?.code === 'FORBIDDEN') { 6 alert('You do not have permission') 7 } else { 8 alert('Something went wrong') 9 } 10 }, 11})

Server-Side Calls

In Server Components

typescript
1// app/users/page.tsx 2import { createCaller } from '@/server/routers/_app' 3import { createContext } from '@/server/context' 4 5export default async function UsersPage() { 6 const ctx = await createContext({ req: {} as any }) 7 const caller = createCaller(ctx) 8 9 const users = await caller.user.list() 10 11 return ( 12 <ul> 13 {users.map(user => ( 14 <li key={user.id}>{user.name}</li> 15 ))} 16 </ul> 17 ) 18}

Create Caller

typescript
1// server/routers/_app.ts 2export const createCaller = createCallerFactory(appRouter) 3 4// Usage 5const caller = createCaller(ctx) 6const user = await caller.user.getById('123')

Advanced Patterns

Request Batching

typescript
1import { httpBatchLink } from '@trpc/client' 2 3const trpcClient = trpc.createClient({ 4 links: [ 5 httpBatchLink({ 6 url: '/api/trpc', 7 maxURLLength: 2083, // Reasonable limit 8 }), 9 ], 10})

Request Deduplication

Automatic with React Query - multiple components requesting same data will only make one request.

Custom Headers

typescript
1const trpcClient = trpc.createClient({ 2 links: [ 3 httpBatchLink({ 4 url: '/api/trpc', 5 headers: () => { 6 return { 7 Authorization: `Bearer ${getToken()}`, 8 } 9 }, 10 }), 11 ], 12})

Error Formatting

typescript
1// server/trpc.ts 2const t = initTRPC.context<Context>().create({ 3 errorFormatter({ shape, error }) { 4 return { 5 ...shape, 6 data: { 7 ...shape.data, 8 zodError: 9 error.cause instanceof ZodError 10 ? error.cause.flatten() 11 : null, 12 }, 13 } 14 }, 15})

Testing

Testing Procedures

typescript
1import { appRouter } from '@/server/routers/_app' 2import { createCaller } from '@/server/routers/_app' 3 4describe('user router', () => { 5 it('creates user', async () => { 6 const ctx = { session: mockSession, db: mockDb } 7 const caller = createCaller(ctx) 8 9 const user = await caller.user.create({ 10 name: 'John', 11 email: 'john@example.com', 12 }) 13 14 expect(user.name).toBe('John') 15 }) 16})

Best Practices

  1. Use Zod for validation - Always validate inputs
  2. Keep procedures small - Single responsibility
  3. Use middleware for auth - Don't repeat auth checks
  4. Type your context - Full type safety
  5. Organize routers - Split into logical domains
  6. Handle errors properly - Use appropriate error codes
  7. Leverage React Query - Use its caching and refetching
  8. Batch requests - Enable batching for better performance
  9. Use optimistic updates - Better UX
  10. Document procedures - Add JSDoc comments

Resources

FAQ & Installation Steps

These questions and steps mirror the structured data on this page for better search understanding.

? Frequently Asked Questions

What is tRPC?

Ideal for TypeScript-focused AI Agents seeking end-to-end typesafe APIs without schemas or code generation. ๐‘€๐‘œ๐’พ ๐ผ๐“‚๐’ถ๐‘”๐’พ๐“ƒ๐’ถ๐“‰๐’พ๐‘œ๐“ƒ๐ฟ๐’ถ๐“ƒ๐’น

How do I install tRPC?

Run the command: npx killer-skills add dblodorn/dmbk-world. It works with Cursor, Windsurf, VS Code, Claude Code, and 19+ other IDEs.

What are the use cases for tRPC?

Key use cases include: Building typesafe APIs with automatic TypeScript inference, Integrating with Next.js for robust frontend-backend interactions, Enhancing API development with autocomplete and type safety features.

Which IDEs are compatible with tRPC?

This skill is compatible with Cursor, Windsurf, VS Code, Trae, Claude Code, OpenClaw, Aider, Codex, OpenCode, Goose, Cline, Roo Code, Kiro, Augment Code, Continue, GitHub Copilot, Sourcegraph Cody, and Amazon Q Developer. Use the Killer-Skills CLI for universal one-command installation.

Are there any limitations for tRPC?

Requires TypeScript setup and configuration. Compatibility limited to Next.js, React Query, and similar frameworks.

โ†“ How To Install

  1. 1. Open your terminal

    Open the terminal or command line in your project directory.

  2. 2. Run the install command

    Run: npx killer-skills add dblodorn/dmbk-world. The CLI will automatically detect your IDE or AI agent and configure the skill.

  3. 3. Start using the skill

    The skill is now active. Your AI agent can use tRPC immediately in the current project.

Related Skills

Looking for an alternative to tRPC or another community skill for your workflow? Explore these related open-source skills.

View All

widget-generator

Logo of f
f

f.k.a. Awesome ChatGPT Prompts. Share, discover, and collect prompts from the community. Free and open source โ€” self-host for your organization with complete privacy.

โ˜… 149.6k
โ‘‚ 0
AI

flags

Logo of vercel
vercel

flags is a Next.js feature management skill that enables developers to efficiently add or modify framework feature flags, streamlining React application development.

โ˜… 138.4k
โ‘‚ 0
Browser

zustand

Logo of lobehub
lobehub

The ultimate space for work and life โ€” to find, build, and collaborate with agent teammates that grow with you. We are taking agent harness to the next level โ€” enabling multi-agent collaboration, effortless agent team design, and introducing agents as the unit of work interaction.

โ˜… 72.8k
โ‘‚ 0
AI

data-fetching

Logo of lobehub
lobehub

The ultimate space for work and life โ€” to find, build, and collaborate with agent teammates that grow with you. We are taking agent harness to the next level โ€” enabling multi-agent collaboration, effortless agent team design, and introducing agents as the unit of work interaction.

โ˜… 72.8k
โ‘‚ 0
AI