Claude Code Plugins

Community-maintained marketplace

Feedback

Scaffold new tRPC API endpoints for the dealflow-network project with proper Zod validation, middleware, database functions, and client hooks. Use when adding new API routes, creating CRUD operations, or extending existing routers.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name add-trpc-endpoint
description Scaffold new tRPC API endpoints for the dealflow-network project with proper Zod validation, middleware, database functions, and client hooks. Use when adding new API routes, creating CRUD operations, or extending existing routers.

Add tRPC Endpoint

Scaffold complete tRPC endpoints following project patterns.

Quick Start

When adding a new endpoint, I will:

  1. Add Zod input schema to server/routers.ts
  2. Create database function in server/db.ts (if needed)
  3. Add procedure with appropriate middleware
  4. Show client usage pattern

Procedure Types

Choose based on auth requirements:

// No authentication required
publicProcedure

// Requires logged-in user (ctx.user available)
protectedProcedure

// Requires admin role (ctx.user.role === 'admin')
adminProcedure

Template: Query Endpoint

// In server/routers.ts - add to appropriate router

const getItem = protectedProcedure
  .input(z.object({
    id: z.number(),
  }))
  .query(async ({ ctx, input }) => {
    const db = await getDb();
    if (!db) throw new TRPCError({ code: "INTERNAL_SERVER_ERROR" });

    const [item] = await db
      .select()
      .from(items)
      .where(eq(items.id, input.id));

    if (!item) {
      throw new TRPCError({ code: "NOT_FOUND", message: "Item not found" });
    }

    return item;
  });

Template: Mutation Endpoint

const createItem = protectedProcedure
  .input(z.object({
    name: z.string().min(1, "Name is required"),
    description: z.string().optional(),
    categoryId: z.number().optional(),
  }))
  .mutation(async ({ ctx, input }) => {
    const db = await getDb();
    if (!db) throw new TRPCError({ code: "INTERNAL_SERVER_ERROR" });

    const [result] = await db.insert(items).values({
      ...input,
      createdBy: ctx.user.id,
      createdAt: new Date(),
    });

    return { id: result.insertId, ...input };
  });

Template: List with Pagination

const listItems = protectedProcedure
  .input(z.object({
    page: z.number().default(1),
    limit: z.number().default(20),
    search: z.string().optional(),
  }))
  .query(async ({ ctx, input }) => {
    const db = await getDb();
    if (!db) throw new TRPCError({ code: "INTERNAL_SERVER_ERROR" });

    const offset = (input.page - 1) * input.limit;

    let query = db.select().from(items);

    if (input.search) {
      query = query.where(like(items.name, `%${input.search}%`));
    }

    const results = await query.limit(input.limit).offset(offset);

    return results;
  });

Adding to Router

// In server/routers.ts
export const appRouter = router({
  // ... existing routers
  items: router({
    list: listItems,
    get: getItem,
    create: createItem,
    update: updateItem,
    delete: deleteItem,
  }),
});

Client Usage

// Query hook
const { data, isLoading, error } = trpc.items.list.useQuery({ page: 1 });

// Mutation hook
const createMutation = trpc.items.create.useMutation({
  onSuccess: () => {
    // Invalidate cache to refetch list
    utils.items.list.invalidate();
    toast.success("Item created");
  },
  onError: (error) => {
    toast.error(`Failed: ${error.message}`);
  },
});

// Call mutation
createMutation.mutate({ name: "New Item" });

Common Zod Patterns

// Required string with min length
name: z.string().min(1, "Required")

// Optional email
email: z.string().email().optional().or(z.literal(""))

// URL validation
linkedinUrl: z.string().url().optional().or(z.literal(""))

// Enum
status: z.enum(["pending", "active", "completed"])

// Array of IDs
tagIds: z.array(z.number())

// Nested object
metadata: z.object({
  source: z.string(),
  confidence: z.number(),
}).optional()

Error Handling

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

// Common error codes
throw new TRPCError({ code: "NOT_FOUND", message: "Item not found" });
throw new TRPCError({ code: "UNAUTHORIZED" });
throw new TRPCError({ code: "FORBIDDEN", message: "Admin only" });
throw new TRPCError({ code: "BAD_REQUEST", message: "Invalid input" });
throw new TRPCError({ code: "INTERNAL_SERVER_ERROR" });

Checklist

  • Input validation with Zod schema
  • Appropriate procedure type (public/protected/admin)
  • Database null check
  • Error handling with TRPCError
  • Add to router export
  • Client cache invalidation strategy