跳至主内容
版本:11.x

输入输出验证器

非官方测试版翻译

本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →

tRPC 过程可为其输入和/或输出定义验证逻辑,验证器还用于推断输入输出类型(若支持则采用 Standard Schema 接口,否则使用适配验证器的自定义接口)。我们为多款流行验证器提供一等支持,也可集成其他验证器

输入验证器

通过定义输入验证器,tRPC 可检查过程调用是否正确,并在出现错误时返回验证失败信息。

使用 procedure.input() 方法设置输入验证器:

ts
// Our examples use Zod by default, but usage with other libraries is identical
import { z } from 'zod';
 
export const t = initTRPC.create();
const publicProcedure = t.procedure;
 
export const appRouter = t.router({
hello: publicProcedure
.input(
z.object({
name: z.string(),
}),
)
.query((opts) => {
const name = opts.input.name;
const name: string
return {
greeting: `Hello ${opts.input.name}`,
};
}),
});
ts
// Our examples use Zod by default, but usage with other libraries is identical
import { z } from 'zod';
 
export const t = initTRPC.create();
const publicProcedure = t.procedure;
 
export const appRouter = t.router({
hello: publicProcedure
.input(
z.object({
name: z.string(),
}),
)
.query((opts) => {
const name = opts.input.name;
const name: string
return {
greeting: `Hello ${opts.input.name}`,
};
}),
});

输入合并

.input() 可堆叠使用以构建更复杂的类型,这在需要为中间件中的过程集合复用公共输入时特别有用。

ts
const baseProcedure = t.procedure
.input(z.object({ townName: z.string() }))
.use((opts) => {
const input = opts.input;
const input: { townName: string; }
 
console.log(`Handling request with user from: ${input.townName}`);
 
return opts.next();
});
 
export const appRouter = t.router({
hello: baseProcedure
.input(
z.object({
name: z.string(),
}),
)
.query((opts) => {
const input = opts.input;
const input: { townName: string; name: string; }
return {
greeting: `Hello ${input.name}, my friend from ${input.townName}`,
};
}),
});
ts
const baseProcedure = t.procedure
.input(z.object({ townName: z.string() }))
.use((opts) => {
const input = opts.input;
const input: { townName: string; }
 
console.log(`Handling request with user from: ${input.townName}`);
 
return opts.next();
});
 
export const appRouter = t.router({
hello: baseProcedure
.input(
z.object({
name: z.string(),
}),
)
.query((opts) => {
const input = opts.input;
const input: { townName: string; name: string; }
return {
greeting: `Hello ${input.name}, my friend from ${input.townName}`,
};
}),
});

输出验证器

由于 tRPC 通过推断过程返回类型自动提供类型安全,输出验证通常不如输入验证重要。定义输出验证器的典型场景包括:

  • 验证来自不可信源的数据是否正确

  • 确保不会向客户端返回多余数据

信息

若输出验证失败,服务器将返回 INTERNAL_SERVER_ERROR 错误。

ts
import { z } from 'zod';
 
export const t = initTRPC.create();
const publicProcedure = t.procedure;
 
export const appRouter = t.router({
hello: publicProcedure
.output(
z.object({
greeting: z.string(),
}),
)
.query((opts) => {
return {
gre,
           
};
}),
});
ts
import { z } from 'zod';
 
export const t = initTRPC.create();
const publicProcedure = t.procedure;
 
export const appRouter = t.router({
hello: publicProcedure
.output(
z.object({
greeting: z.string(),
}),
)
.query((opts) => {
return {
gre,
           
};
}),
});

订阅的输出验证

由于订阅是异步迭代器,可采用与前述相同的验证技术。

详见订阅指南获取更多信息。

最基础的验证器:函数

您无需任何第三方依赖,仅用函数即可定义验证器。

信息

除非有特殊需求,否则我们不建议创建自定义验证器。但需要理解的是:这里没有魔法——仅仅是 TypeScript!

多数情况下建议使用验证库

ts
import { initTRPC } from '@trpc/server';
 
export const t = initTRPC.create();
 
const publicProcedure = t.procedure;
 
export const appRouter = t.router({
hello: publicProcedure
.input((value): string => {
if (typeof value === 'string') {
return value;
}
throw new Error('Input is not a string');
})
.output((value): string => {
if (typeof value === 'string') {
return value;
}
throw new Error('Output is not a string');
})
.query((opts) => {
const { input } = opts;
const input: string
return `hello ${input}`;
}),
});
 
export type AppRouter = typeof appRouter;
ts
import { initTRPC } from '@trpc/server';
 
export const t = initTRPC.create();
 
const publicProcedure = t.procedure;
 
export const appRouter = t.router({
hello: publicProcedure
.input((value): string => {
if (typeof value === 'string') {
return value;
}
throw new Error('Input is not a string');
})
.output((value): string => {
if (typeof value === 'string') {
return value;
}
throw new Error('Output is not a string');
})
.query((opts) => {
const { input } = opts;
const input: string
return `hello ${input}`;
}),
});
 
export type AppRouter = typeof appRouter;

库集成方案

tRPC 开箱即支持多款主流验证解析库,包括所有符合 Standard Schema 的库。以下是我们官方维护的部分验证器使用示例:

使用 Zod

Zod 是我们的默认推荐方案,其强大的生态体系使其成为代码库多模块复用的理想选择。若无特殊偏好且需要满足未来扩展的强力库,Zod 是绝佳选择。

ts
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
 
export const t = initTRPC.create();
 
const publicProcedure = t.procedure;
 
export const appRouter = t.router({
hello: publicProcedure
.input(
z.object({
name: z.string(),
}),
)
.output(
z.object({
greeting: z.string(),
}),
)
.query(({ input }) => {
(parameter) input: { name: string; }
return {
greeting: `hello ${input.name}`,
};
}),
});
 
export type AppRouter = typeof appRouter;
ts
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
 
export const t = initTRPC.create();
 
const publicProcedure = t.procedure;
 
export const appRouter = t.router({
hello: publicProcedure
.input(
z.object({
name: z.string(),
}),
)
.output(
z.object({
greeting: z.string(),
}),
)
.query(({ input }) => {
(parameter) input: { name: string; }
return {
greeting: `hello ${input.name}`,
};
}),
});
 
export type AppRouter = typeof appRouter;

使用 Yup

ts
import { initTRPC } from '@trpc/server';
import * as yup from 'yup';
 
export const t = initTRPC.create();
 
const publicProcedure = t.procedure;
 
export const appRouter = t.router({
hello: publicProcedure
.input(
yup.object({
name: yup.string().required(),
}),
)
.output(
yup.object({
greeting: yup.string().required(),
}),
)
.query(({ input }) => {
(parameter) input: { name: string; }
return {
greeting: `hello ${input.name}`,
};
}),
});
 
export type AppRouter = typeof appRouter;
ts
import { initTRPC } from '@trpc/server';
import * as yup from 'yup';
 
export const t = initTRPC.create();
 
const publicProcedure = t.procedure;
 
export const appRouter = t.router({
hello: publicProcedure
.input(
yup.object({
name: yup.string().required(),
}),
)
.output(
yup.object({
greeting: yup.string().required(),
}),
)
.query(({ input }) => {
(parameter) input: { name: string; }
return {
greeting: `hello ${input.name}`,
};
}),
});
 
export type AppRouter = typeof appRouter;

使用 Superstruct

ts
import { initTRPC } from '@trpc/server';
import { object, string } from 'superstruct';
 
export const t = initTRPC.create();
 
const publicProcedure = t.procedure;
 
export const appRouter = t.router({
hello: publicProcedure
.input(object({ name: string() }))
.output(object({ greeting: string() }))
.query(({ input }) => {
(parameter) input: { name: string; }
return {
greeting: `hello ${input.name}`,
};
}),
});
 
export type AppRouter = typeof appRouter;
ts
import { initTRPC } from '@trpc/server';
import { object, string } from 'superstruct';
 
export const t = initTRPC.create();
 
const publicProcedure = t.procedure;
 
export const appRouter = t.router({
hello: publicProcedure
.input(object({ name: string() }))
.output(object({ greeting: string() }))
.query(({ input }) => {
(parameter) input: { name: string; }
return {
greeting: `hello ${input.name}`,
};
}),
});
 
export type AppRouter = typeof appRouter;

使用 scale-ts

ts
import { initTRPC } from '@trpc/server';
import * as $ from 'scale-codec';
 
export const t = initTRPC.create();
 
const publicProcedure = t.procedure;
 
export const appRouter = t.router({
hello: publicProcedure
.input($.object($.field('name', $.str)))
.output($.object($.field('greeting', $.str)))
.query(({ input }) => {
(parameter) input: { readonly name: string; }
return {
greeting: `hello ${input.name}`,
};
}),
});
 
export type AppRouter = typeof appRouter;
ts
import { initTRPC } from '@trpc/server';
import * as $ from 'scale-codec';
 
export const t = initTRPC.create();
 
const publicProcedure = t.procedure;
 
export const appRouter = t.router({
hello: publicProcedure
.input($.object($.field('name', $.str)))
.output($.object($.field('greeting', $.str)))
.query(({ input }) => {
(parameter) input: { readonly name: string; }
return {
greeting: `hello ${input.name}`,
};
}),
});
 
export type AppRouter = typeof appRouter;

使用 Typia

ts
import { initTRPC } from '@trpc/server';
import typia from 'typia';
import { v4 } from 'uuid';
import { IBbsArticle } from '../structures/IBbsArticle';
const t = initTRPC.create();
const publicProcedure = t.procedure;
export const appRouter = t.router({
store: publicProcedure
.input(typia.createAssert<IBbsArticle.IStore>())
.output(typia.createAssert<IBbsArticle>())
.query(({ input }) => {
return {
id: v4(),
writer: input.writer,
title: input.title,
body: input.body,
created_at: new Date().toString(),
};
}),
});
export type AppRouter = typeof appRouter;
ts
import { initTRPC } from '@trpc/server';
import typia from 'typia';
import { v4 } from 'uuid';
import { IBbsArticle } from '../structures/IBbsArticle';
const t = initTRPC.create();
const publicProcedure = t.procedure;
export const appRouter = t.router({
store: publicProcedure
.input(typia.createAssert<IBbsArticle.IStore>())
.output(typia.createAssert<IBbsArticle>())
.query(({ input }) => {
return {
id: v4(),
writer: input.writer,
title: input.title,
body: input.body,
created_at: new Date().toString(),
};
}),
});
export type AppRouter = typeof appRouter;

使用 ArkType

ts
import { initTRPC } from '@trpc/server';
import { type } from 'arktype';
export const t = initTRPC.create();
const publicProcedure = t.procedure;
export const appRouter = t.router({
hello: publicProcedure.input(type({ name: 'string' })).query((opts) => {
return {
greeting: `hello ${opts.input.name}`,
};
}),
});
export type AppRouter = typeof appRouter;
ts
import { initTRPC } from '@trpc/server';
import { type } from 'arktype';
export const t = initTRPC.create();
const publicProcedure = t.procedure;
export const appRouter = t.router({
hello: publicProcedure.input(type({ name: 'string' })).query((opts) => {
return {
greeting: `hello ${opts.input.name}`,
};
}),
});
export type AppRouter = typeof appRouter;

使用 effect

ts
import { initTRPC } from '@trpc/server';
import { Schema } from 'effect';
export const t = initTRPC.create();
const publicProcedure = t.procedure;
export const appRouter = t.router({
hello: publicProcedure
.input(Schema.standardSchemaV1(Schema.Struct({ name: Schema.String })))
.output(Schema.standardSchemaV1(Schema.Struct({ greeting: Schema.String })))
.query(({ input }) => {
// ^?
return {
greeting: `hello ${input.name}`,
};
}),
});
export type AppRouter = typeof appRouter;
ts
import { initTRPC } from '@trpc/server';
import { Schema } from 'effect';
export const t = initTRPC.create();
const publicProcedure = t.procedure;
export const appRouter = t.router({
hello: publicProcedure
.input(Schema.standardSchemaV1(Schema.Struct({ name: Schema.String })))
.output(Schema.standardSchemaV1(Schema.Struct({ greeting: Schema.String })))
.query(({ input }) => {
// ^?
return {
greeting: `hello ${input.name}`,
};
}),
});
export type AppRouter = typeof appRouter;

使用 Valibot

ts
import { initTRPC } from '@trpc/server';
import * as v from 'valibot';
 
export const t = initTRPC.create();
 
const publicProcedure = t.procedure;
 
export const appRouter = t.router({
hello: publicProcedure
.input(v.object({ name: v.string() }))
.output(v.object({ greeting: v.string() }))
.query(({ input }) => {
(parameter) input: { name: string; }
return {
greeting: `hello ${input.name}`,
};
}),
});
 
export type AppRouter = typeof appRouter;
ts
import { initTRPC } from '@trpc/server';
import * as v from 'valibot';
 
export const t = initTRPC.create();
 
const publicProcedure = t.procedure;
 
export const appRouter = t.router({
hello: publicProcedure
.input(v.object({ name: v.string() }))
.output(v.object({ greeting: v.string() }))
.query(({ input }) => {
(parameter) input: { name: string; }
return {
greeting: `hello ${input.name}`,
};
}),
});
 
export type AppRouter = typeof appRouter;

使用 @robolex/sure

您可按需定义自定义错误类型及错误抛出函数。为简化操作,@robolex/sure 提供了 sure/src/err.ts

ts
// sure/src/err.ts
export const err = (schema) => (input) => {
const [good, result] = schema(input);
if (good) return result;
throw result;
};
ts
// sure/src/err.ts
export const err = (schema) => (input) => {
const [good, result] = schema(input);
if (good) return result;
throw result;
};
ts
import { err, object, string } from '@robolex/sure';
import { initTRPC } from '@trpc/server';
export const t = initTRPC.create();
const publicProcedure = t.procedure;
export const appRouter = t.router({
hello: publicProcedure
.input(
err(
object({
name: string,
}),
),
)
.output(
err(
object({
greeting: string,
}),
),
)
.query(({ input }) => {
// ^?
return {
greeting: `hello ${input.name}`,
};
}),
});
export type AppRouter = typeof appRouter;
ts
import { err, object, string } from '@robolex/sure';
import { initTRPC } from '@trpc/server';
export const t = initTRPC.create();
const publicProcedure = t.procedure;
export const appRouter = t.router({
hello: publicProcedure
.input(
err(
object({
name: string,
}),
),
)
.output(
err(
object({
greeting: string,
}),
),
)
.query(({ input }) => {
// ^?
return {
greeting: `hello ${input.name}`,
};
}),
});
export type AppRouter = typeof appRouter;

使用 TypeBox

ts
import { Type } from '@sinclair/typebox';
import { initTRPC } from '@trpc/server';
import { wrap } from '@typeschema/typebox';
export const t = initTRPC.create();
const publicProcedure = t.procedure;
export const appRouter = t.router({
hello: publicProcedure
.input(wrap(Type.Object({ name: Type.String() })))
.output(wrap(Type.Object({ greeting: Type.String() })))
.query(({ input }) => {
return {
greeting: `hello ${input.name}`,
};
}),
});
export type AppRouter = typeof appRouter;
ts
import { Type } from '@sinclair/typebox';
import { initTRPC } from '@trpc/server';
import { wrap } from '@typeschema/typebox';
export const t = initTRPC.create();
const publicProcedure = t.procedure;
export const appRouter = t.router({
hello: publicProcedure
.input(wrap(Type.Object({ name: Type.String() })))
.output(wrap(Type.Object({ greeting: Type.String() })))
.query(({ input }) => {
return {
greeting: `hello ${input.name}`,
};
}),
});
export type AppRouter = typeof appRouter;

贡献自定义验证库

若您维护支持 tRPC 的验证库,欢迎提交 PR 补充本页面的等效用法示例并附上文档链接。

多数情况下集成验证器只需满足现有类型接口之一。建议优先实现 Standard Schema,特殊情况下我们也会接受新增接口的 PR。欢迎提交 issue 讨论。具体支持的解析/验证接口和函数可查阅源码