agentsclimarketplace

Trpc typesafe api

Skill findscripter/everything-skills/02-engineering/trpc-typesafe-api

类书式 AI Agent 技能大典 · 精选/中文化/互见成网的 500+ 开源技能,可作为 Claude Code 插件市场一键安装。A curated, cross-referenced encyclopedia of 500+ open-source agent skills.

Install
npx -y skills add findscripter/everything-skills --skill trpc-typesafe-api

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

当用 TypeScript 单仓前后端(Next.js/Remix/Express+React)需要免 Schema、免代码生成的端到端类型安全 API,或为已有 tRPC 加鉴权中间件、订阅、SSR 调用时使用;做 router/procedure 设计、Zod 输入校验、双 context 工厂、protectedProcedure、客户端 React Query 接线并产出可落地代码;不适用于跨语言/异构客户端、纯 REST/GraphQL 契约、裸 SQL 或 ORM 调优。触发词:tRPC、router、procedure、Zod、createTRPCContext、protectedProcedure、useQuery

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

11.0 KB, ~2.9k tokens by cl100k_base, as published. Nobody here has run it

何时使用

适用:

  • 用 TypeScript 搭建前后端同仓的全栈应用(Next.js、Remix、Express + React),希望服务端类型直接流到客户端,调用即自动补全、编译期校验、重构安全。
  • 想要端到端类型安全、又不愿背 REST/GraphQL 的 Schema 或代码生成负担。
  • 为已有 tRPC 项目新增实时订阅(subscription / WebSocket)。
  • 设计多级中间件(鉴权、限流、租户隔离)挂到 procedure 上。
  • 把现有 REST/GraphQL 接口增量迁移到 tRPC。

不该用:

  • 客户端与服务端不共享 TypeScript 类型(移动原生、第三方异构调用方、跨语言)—— tRPC 的类型流失效,应给出 OpenAPI/GraphQL 等显式契约。
  • 需求是裸 SQL 调优或 ORM(Prisma 等)建模,转交对应专项。
  • 仅需对外暴露公开稳定的 HTTP 契约(公共 API、Webhook 回调)。

核心概念

  • Router / Procedure:router 把相关 procedure 分组(≈端点)。procedure 是类型化函数:query 读、mutation 写、subscription 实时流。
  • Zod 输入校验:所有 procedure 输入用 Zod 校验,handler 里拿到的是已校验、已推导类型的 input,无需手动解析。
  • Context:每请求构建一次的共享状态(auth session、db 客户端、headers)。关键:Next.js App Router 与 Pages Router 必须用不同的 context 工厂——App Router handler 收到的是 fetch Request,不是 Node 的 NextApiRequest
  • Middleware:在 procedure 前链式执行,用于鉴权、日志、上下文增强,可为下游 procedure 收窄/扩展 context 类型。

步骤 / 指令

  1. 安装并初始化 tRPC 实例与可复用构建器(router / publicProcedure / middleware),在 errorFormatter 里透出 Zod 扁平化错误。
  2. 定义两个 context 工厂createTRPCContext(HTTP handler 用,接 FetchCreateContextFnOptions)与 createServerContext(Server Component / RSC / cron 等服务端直调用,无 HTTP 请求)。Context 类型从 createTRPCContext 返回值推导。
  3. 写鉴权 middleware(enforceAuth):session 缺失抛 TRPCError({ code: 'UNAUTHORIZED' }),并在 next({ ctx }) 里收窄 session 为非空,导出 protectedProcedure = t.procedure.use(enforceAuth)
  4. 按领域拆 router(post、user、billing…),public 用 publicProcedure、需登录用 protectedProcedure,每个 procedure 用 .input(z....) 校验。
  5. root.ts 合并为 appRouter,并 只导出类型 export type AppRouter = typeof appRouter(客户端永不 import appRouter 实例)。
  6. 挂载 API handler(App Router):用 fetchRequestHandler + fetch 版 context 工厂,导出为 GET / POST
  7. 客户端接线:createTRPCReact<AppRouter>()trpc,用 httpBatchLinkQueryClientProvider 包裹应用。订阅需 splitLink 把 subscription 路由到 wsLink、query/mutation 到 httpBatchLink

示例

安装:

npm install @trpc/server @trpc/client @trpc/react-query @tanstack/react-query zod

实例与 Zod 错误透出(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;

双 context 工厂(src/server/context.ts):

import { type FetchCreateContextFnOptions } from '@trpc/server/adapters/fetch';
import { auth } from '@/server/auth';
import { db } from './db';

// App Router Route Handler 用:opts.req 是 fetch Request
export async function createTRPCContext(opts: FetchCreateContextFnOptions) {
  const session = await auth();
  return { session, db, headers: opts.req.headers };
}

// Server Component / RSC / cron 直调用:无 HTTP 请求,直接 auth()
export async function createServerContext() {
  const session = await auth();
  return { session, db };
}

export type Context = Awaited<ReturnType<typeof createTRPCContext>>;

鉴权中间件与受保护 procedure:

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

export const protectedProcedure = t.procedure.use(enforceAuth);

带分页游标的 router(src/server/routers/post.ts 节选):

export const postRouter = router({
  list: publicProcedure
    .input(z.object({
      limit: z.number().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' },
      });
      const nextCursor = posts.length > input.limit ? posts.pop()!.id : undefined;
      return { posts, nextCursor };
    }),

  create: protectedProcedure
    .input(z.object({ title: z.string().min(1).max(200), body: z.string().min(1) }))
    .mutation(async ({ ctx, input }) =>
      ctx.db.post.create({ data: { ...input, authorId: ctx.session.user.id } })
    ),
});

合并根 router 并只导出类型(src/server/root.ts):

export const appRouter = router({ post: postRouter, user: userRouter });
export type AppRouter = typeof appRouter; // 客户端只 import 这个 type

App Router handler(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,
    createContext: (opts: FetchCreateContextFnOptions) => createTRPCContext(opts),
  });

export { handler as GET, handler as POST };

客户端组件查询 + mutation 后失效缓存:

'use client';
import { trpc } from '@/utils/trpc';

// 查询
const { data, isLoading, error } = trpc.post.list.useQuery({ limit: 10 });

// mutation:onSuccess 里 invalidate 触发 React Query 重取
const utils = trpc.useUtils();
const createPost = trpc.post.create.useMutation({
  onSuccess: () => utils.post.list.invalidate(),
});

服务端调用方(Server Component / SSR)—— 用 createServerContext,避免造空请求对象:

import { createCallerFactory } from '@trpc/server';
import { appRouter } from '@/server/root';
import { createServerContext } from '@/server/context';

const createCaller = createCallerFactory(appRouter);
const caller = createCaller(await createServerContext());
const { posts } = await caller.post.list({ limit: 20 });

实时订阅(节选):服务端用 observable 包裹 EventEmitter,客户端 trpc.notification.onNew.useSubscription(undefined, { onData }),客户端配置需 wsLink

注意事项

最佳实践:

  • 只从服务端导出 AppRouter 类型,客户端用 import type { AppRouter },绝不 import appRouter 实例。
  • HTTP handler 用 createTRPCContext、Server Component / caller 用 createServerContext,两个工厂分开。
  • 所有输入用 Zod 校验(含分页游标、ID),不信任裸 input
  • 按领域拆 router,在 root.ts 合并;中间件里扩展 context,避免一请求多次查库。
  • mutation 后用 utils.<router>.<procedure>.invalidate() 保持缓存新鲜。
  • 客户端实例按 provider 创建,勿全局共享(避免陈旧闭包)。

安全:

  • 鉴权一律在 protectedProcedure / middleware 强制执行,绝不只靠客户端校验。
  • TRPCError 返回公开安全的 message,内部错误细节与堆栈只留服务端。
  • 公开 procedure 用 middleware 限流防滥用。

常见坑:

  • protectedProcedure 里 session 恒为 null:检查 createTRPCContext 是否调用了正确的服务端 auth(),且 App Router handler 没有用 as any 塞 Pages Router 的 req/res禁止用 as any 强转 context——类型不匹配会在 auth/session 取值返回 undefined 时变成运行时崩溃。
  • Server Component caller 鉴权查询失败:用 createServerContext(),别给 createContext 传空对象或 {} as any
  • Type error: AppRouter is not assignable to AnyRouter:客户端用 import type 导入 AppRouter,不要导入整个模块。
  • mutation 后 UI 不更新:在 onSuccessinvalidate()
  • Cannot find module '@trpc/server/adapters/next':App Router 用 @trpc/server/adapters/fetch + fetchRequestHandlernextjs adapter 仅限 Pages Router。
  • 订阅连不上:需 splitLink 把 subscription 走 wsLink、query/mutation 走 httpBatchLink
  • 别把业务逻辑写进 route handler,留在 procedure 或 service 层。

互见

  • related:nestjs-expert —— 另一条 Node 后端路线,需要面向公开/异构客户端的契约时可对比选型
  • related:react-state-management —— tRPC 客户端基于 React Query,服务端状态缓存与失效策略相通
  • combines_with:prisma-orm-expert —— procedure handler 里常用 Prisma 作为 ctx.db,配合做类型安全的数据访问层

采编自 sickn33/antigravity-awesome-skills(MIT)。

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 327,132. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.