Claude Code Plugins

Community-maintained marketplace

Feedback

|

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 new-api-route
description Create new ORPC API routes with contracts, routers, and client integration. 创建新的 ORPC API 路由,包含合约、路由器和客户端集成。 Use when: - Adding new API endpoints - Creating CRUD operations for a new entity - User mentions "new API", "add endpoint", "create route", "新增接口", "添加 API"

New API Route Skill / 新增 API 路由技能

设计思路 / Design Notes:

  1. 这是一个多步骤工作流,需要修改多个文件
  2. 提供完整的代码模板,保持项目一致性
  3. 明确文件位置和修改顺序
  4. 包含常见模式(CRUD、分页等)

Overview / 概述

Create type-safe ORPC API routes following the project's contract-first pattern. 按照项目的合约优先模式创建类型安全的 ORPC API 路由。

Step-by-Step Instructions / 分步指令

Step 1: Create Contract / 创建合约

File: packages/api/src/contracts/<entity>.ts

// [IN]: @orpc/contract, valibot / 依赖 oRPC 合约及验证器
// [OUT]: <entity>Contract object / 导出 <entity> API 合约定义
// [POS]: API layer - <Entity> contract definition / API 层 - <实体>合约定义
// Protocol: When updating me, sync this header + parent folder's .folder.md
// 协议:更新本文件时,同步更新此头注释及所属文件夹的 .folder.md

import { oc } from '@orpc/contract';
import * as v from 'valibot';

const <entity>Contract = oc
  .prefix('/<entities>')
  .tag('<entity>')
  .router({
    // GET /<entities> - List all
    all: oc
      .route({ method: 'GET', path: '/', summary: 'List all <entities>' })
      .output(v.array(v.object({
        id: v.string(),
        // ... other fields
      }))),

    // GET /<entities>/{id} - Get one
    one: oc
      .route({ method: 'GET', path: '/{id}', summary: 'Get <entity> by ID' })
      .input(v.object({ id: v.pipe(v.string(), v.uuid()) }))
      .output(v.object({
        id: v.string(),
        // ... all fields
      })),

    // POST /<entities> - Create
    create: oc
      .route({ method: 'POST', path: '/', summary: 'Create <entity>' })
      .input(Create<Entity>Schema)
      .output(v.object({})),

    // DELETE /<entities>/{id} - Delete
    delete: oc
      .route({ method: 'DELETE', path: '/{id}', summary: 'Delete <entity>' })
      .input(v.object({ id: v.pipe(v.string(), v.uuid()) }))
      .output(v.object({})),
  });

export default <entity>Contract;

Step 2: Register Contract / 注册合约

File: packages/api/src/contracts/index.ts

import <entity>Contract from './<entity>';

export const appContract = oc
  .errors({ /* existing errors */ })
  .router({
    // ... existing routes
    <entity>: <entity>Contract,  // 👈 Add new contract
  });

Step 3: Create Router / 创建路由器

File: packages/api/src/server/router/<entity>.ts

// [IN]: @repo/db, ../orpc / 依赖数据库及 oRPC 过程
// [OUT]: <entity>Router object / 导出 <entity> 路由处理器
// [POS]: API layer - <Entity> CRUD handlers / API 层 - <实体> CRUD 处理器
// Protocol: When updating me, sync this header + parent folder's .folder.md
// 协议:更新本文件时,同步更新此头注释及所属文件夹的 .folder.md

import { desc, eq } from '@repo/db';
import { <entity> } from '@repo/db/schema';
import { protectedProcedure } from '../orpc';

const <entity>Router = {
  all: protectedProcedure.<entity>.all.handler(({ context }) => {
    return context.db.query.<entity>.findMany({
      orderBy: desc(<entity>.createdAt),
    });
  }),

  one: protectedProcedure.<entity>.one.handler(
    async ({ context, input, errors }) => {
      const [result] = await context.db
        .select()
        .from(<entity>)
        .where(eq(<entity>.id, input.id));

      if (!result) {
        throw errors.NOT_FOUND({ message: `<Entity> not found` });
      }
      return result;
    }
  ),

  create: protectedProcedure.<entity>.create.handler(
    async ({ context, input }) => {
      await context.db.insert(<entity>).values({
        ...input,
        createdBy: context.session.user.id,
      });
      return {};
    }
  ),

  delete: protectedProcedure.<entity>.delete.handler(
    async ({ context, input, errors }) => {
      const res = await context.db
        .delete(<entity>)
        .where(eq(<entity>.id, input.id));

      if (res.rowCount === 0) {
        throw errors.NOT_FOUND({ message: `<Entity> not found` });
      }
      return {};
    }
  ),
};

export default <entity>Router;

Step 4: Register Router / 注册路由器

File: packages/api/src/server/router/index.ts

import <entity>Router from './<entity>';

export const appRouter = {
  // ... existing routers
  <entity>: <entity>Router,  // 👈 Add new router
};

Step 5: Update Documentation / 更新文档

  1. Update .folder.md files in affected directories
  2. Run pnpm doc-lint to verify

Step 6: Push Schema (if new table) / 推送模式(如果是新表)

pnpm db:push

Client Usage Example / 客户端使用示例

// In apps/web
import { apiClient } from '@/clients/apiClient';

// List
const { data } = apiClient.<entity>.all.useQuery();

// Create
const mutation = apiClient.<entity>.create.useMutation();
await mutation.mutateAsync({ /* input */ });

Reference / 参考

  • Existing example: packages/api/src/contracts/posts.ts
  • Router example: packages/api/src/server/router/post.ts