trpc-router
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseTRPC Router Guide
TRPC 路由指南
File Location
文件位置
- Routers:
src/server/routers/lambda/<domain>.ts - Helpers:
src/server/routers/lambda/_helpers/ - Schemas:
src/server/routers/lambda/_schema/
- 路由文件:
src/server/routers/lambda/<domain>.ts - 辅助工具:
src/server/routers/lambda/_helpers/ - 校验Schema:
src/server/routers/lambda/_schema/
Router Structure
路由结构
Imports
导入
typescript
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';typescript
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';Middleware: Inject Models into ctx
中间件:将模型注入ctx
Always use middleware to inject models into instead of creating inside every procedure.
ctxnew Model(ctx.serverDB, ctx.userId)typescript
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),
},
});
});Then use in procedures:
ctx.fooModeltypescript
// Good
const model = ctx.fooModel;
// Bad - don't create models inside procedures
const model = new FooModel(ctx.serverDB, ctx.userId);Exception: When a model needs a different (e.g., watchdog iterating over multiple users' tasks), create it inline.
userId请始终使用中间件将模型注入到中,不要在每个procedure内部创建实例。
ctxnew Model(ctx.serverDB, ctx.userId)typescript
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),
},
});
});之后你可以在procedure中直接使用:
ctx.fooModeltypescript
// Good
const model = ctx.fooModel;
// Bad - don't create models inside procedures
const model = new FooModel(ctx.serverDB, ctx.userId);例外情况:如果模型需要使用不同的(例如监控程序遍历多个用户的任务),可以在procedure内直接创建实例。
userIdProcedure Pattern
Procedure编写范式
typescript
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',
});
}
}),
});typescript
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',
});
}
}),
});Aggregated Detail Endpoint
聚合详情端点
For views that need multiple related data, create a single procedure that fetches everything in parallel:
detailtypescript
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,
};
}),This avoids the CLI or frontend making N sequential requests.
对于需要多个关联数据的视图,可创建单个 procedure并行拉取所有所需数据:
detailtypescript
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个串行请求。
Conventions
开发规范
- Return shape: for queries,
{ data, success: true }for mutations{ data?, message, success: true } - Error handling: re-throw , wrap others with
TRPCError+ newconsole.errorTRPCError - Input validation: use schemas, define at file top
zod - Router name:
export const fooRouter = router({ ... }) - Procedure names: alphabetical order within the router object
- Log prefix: format, e.g.
[domain:procedure][task:create]
- 返回格式:查询类请求返回,变更类请求返回
{ data, success: true }{ data?, message, success: true } - 错误处理:遇到直接抛出,其他错误需先通过
TRPCError打印日志,再封装为新的console.error抛出TRPCError - 输入校验:使用schema,在文件顶部定义
zod - 路由命名:遵循格式
export const fooRouter = router({ ... }) - Procedure命名:在路由对象内按字母顺序排列
- 日志前缀:使用格式,例如
[领域:procedure名称][task:create]