关于 trpc-router
面向服务端开发的 tRPC 路由编写指南,涵盖过程定义、通过中间件注入模型、输入校验、错误处理及端点约定。
主要功能
- 01通过中间件注入模型
- 02使用 Zod 校验输入
- 03查询与变更过程模式
- 04聚合详情端点
- 05路由命名与日志约定
适用场景
- 01添加路由过程
- 02在一个端点获取关联数据
- 03统一路由的错误处理
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ lobehub/ lobehub --skill trpc-router安装后在 Claude Code 中输入/trpc-router确认已加载。
详细介绍
TRPC 路由指南
文件位置
- 路由:
apps/server/src/routers/lambda/<domain>.ts - 辅助函数:
apps/server/src/routers/lambda/_helpers/ - Schema:
apps/server/src/routers/lambda/_schema/
路由结构
导入
import { TRPCError } from '@trpc/server';
import { z } from 'zod';
import { SomeModel } from '@/database/models/some';
import { authedProcedure, router } from '@/libs/trpc/lambda';
import { serverDatabase } from '@/libs/trpc/lambda/middleware';
中间件:将模型注入 ctx
始终使用中间件将模型注入 ctx,而不是在每个过程内部创建 new Model(ctx.serverDB, ctx.userId)。
const domainProcedure = authedProcedure.use(serverDatabase).use(async (opts) => {
const { ctx } = opts;
return opts.next({
ctx: {
fooModel: new FooModel(ctx.serverDB, ctx.userId),
barModel: new BarModel(ctx.serverDB, ctx.userId),
},
});
});
然后在过程中使用 ctx.fooModel:
// Good
const model = ctx.fooModel;
// Bad - don't create models inside procedures
const model = new FooModel(ctx.serverDB, ctx.userId);
例外:当模型需要不同的 userId 时(例如,watchdog 遍历多个用户的任务),请直接创建模型。
过程模式
export const fooRouter = router({
// Query
find: domainProcedure.input(z.object({ id: z.string() })).query(async ({ input, ctx }) => {
try {
const item = await ctx.fooModel.findById(input.id);
if (!item) throw new TRPCError({ code: 'NOT_FOUND', message: 'Not found' });
return { data: item, success: true };
} catch (error) {
if (error instanceof TRPCError) throw error;
console.error('[foo:find]', error);
throw new TRPCError({
cause: error,
code: 'INTERNAL_SERVER_ERROR',
message: 'Failed to find item',
});
}
}),
// Mutation
create: domainProcedure.input(createSchema).mutation(async ({ input, ctx }) => {
try {
const item = await ctx.fooModel.create(input);
return { data: item, message: 'Created', success: true };
} catch (error) {
if (error instanceof TRPCError) throw error;
console.error('[foo:create]', error);
throw new TRPCError({
cause: error,
code: 'INTERNAL_SERVER_ERROR',
message: 'Failed to create',
});
}
}),
});
聚合详情端点
对于需要多个相关数据的视图,创建单个 detail 过程,并行获取所有数据:
detail: domainProcedure.input(idInput).query(async ({ input, ctx }) => {
const item = await resolveOrThrow(ctx.fooModel, input.id);
const [children, related] = await Promise.all([
ctx.fooModel.findChildren(item.id),
ctx.barModel.findByFooId(item.id),
]);
return {
data: { ...item, children, related },
success: true,
};
}),
这样可以避免 CLI 或前端按顺序发起 N 次请求。
约定
- 返回结构:查询使用
{ data, success: true },变更使用{ data?, message, success: true } - 错误处理:重新抛出
TRPCError,其他错误则使用console.error+ 新建TRPCError包装 - 输入验证:使用
zodschema,并在文件顶部定义 - 路由名称:
export const fooRouter = router({ ... }) - 过程名称:在路由对象内按字母顺序排列
- 日志前缀:使用
[domain:procedure]格式,例如[task:create]
评论
支持 Markdown