输入输出验证器
本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →
tRPC 过程可为其输入和/或输出定义验证逻辑,验证器还用于推断输入输出类型(若支持则采用 Standard Schema 接口,否则使用适配验证器的自定义接口)。我们为多款流行验证器提供一等支持,也可集成其他验证器。
输入验证器
通过定义输入验证器,tRPC 可检查过程调用是否正确,并在出现错误时返回验证失败信息。
使用 procedure.input() 方法设置输入验证器:
ts// Our examples use Zod by default, but usage with other libraries is identicalimport {z } from 'zod';export constt =initTRPC .create ();constpublicProcedure =t .procedure ;export constappRouter =t .router ({hello :publicProcedure .input (z .object ({name :z .string (),}),).query ((opts ) => {constname =opts .input .name ;return {greeting : `Hello ${opts .input .name }`,};}),});
ts// Our examples use Zod by default, but usage with other libraries is identicalimport {z } from 'zod';export constt =initTRPC .create ();constpublicProcedure =t .procedure ;export constappRouter =t .router ({hello :publicProcedure .input (z .object ({name :z .string (),}),).query ((opts ) => {constname =opts .input .name ;return {greeting : `Hello ${opts .input .name }`,};}),});
输入合并
.input() 可堆叠使用以构建更复杂的类型,这在需要为中间件中的过程集合复用公共输入时特别有用。
tsconstbaseProcedure =t .procedure .input (z .object ({townName :z .string () })).use ((opts ) => {constinput =opts .input ;console .log (`Handling request with user from: ${input .townName }`);returnopts .next ();});export constappRouter =t .router ({hello :baseProcedure .input (z .object ({name :z .string (),}),).query ((opts ) => {constinput =opts .input ;return {greeting : `Hello ${input .name }, my friend from ${input .townName }`,};}),});
tsconstbaseProcedure =t .procedure .input (z .object ({townName :z .string () })).use ((opts ) => {constinput =opts .input ;console .log (`Handling request with user from: ${input .townName }`);returnopts .next ();});export constappRouter =t .router ({hello :baseProcedure .input (z .object ({name :z .string (),}),).query ((opts ) => {constinput =opts .input ;return {greeting : `Hello ${input .name }, my friend from ${input .townName }`,};}),});
输出验证器
由于 tRPC 通过推断过程返回类型自动提供类型安全,输出验证通常不如输入验证重要。定义输出验证器的典型场景包括:
-
验证来自不可信源的数据是否正确
-
确保不会向客户端返回多余数据
若输出验证失败,服务器将返回 INTERNAL_SERVER_ERROR 错误。
tsimport {z } from 'zod';export constt =initTRPC .create ();constpublicProcedure =t .procedure ;export constappRouter =t .router ({hello :publicProcedure .output (z .object ({greeting :z .string (),}),).query ((opts ) => {return {gre ,};}),});
tsimport {z } from 'zod';export constt =initTRPC .create ();constpublicProcedure =t .procedure ;export constappRouter =t .router ({hello :publicProcedure .output (z .object ({greeting :z .string (),}),).query ((opts ) => {return {gre ,};}),});
订阅的输出验证
由于订阅是异步迭代器,可采用与前述相同的验证技术。
详见订阅指南获取更多信息。
最基础的验证器:函数
您无需任何第三方依赖,仅用函数即可定义验证器。
除非有特殊需求,否则我们不建议创建自定义验证器。但需要理解的是:这里没有魔法——仅仅是 TypeScript!
多数情况下建议使用验证库。
tsimport {initTRPC } from '@trpc/server';export constt =initTRPC .create ();constpublicProcedure =t .procedure ;export constappRouter =t .router ({hello :publicProcedure .input ((value ): string => {if (typeofvalue === 'string') {returnvalue ;}throw newError ('Input is not a string');}).output ((value ): string => {if (typeofvalue === 'string') {returnvalue ;}throw newError ('Output is not a string');}).query ((opts ) => {const {input } =opts ;return `hello ${input }`;}),});export typeAppRouter = typeofappRouter ;
tsimport {initTRPC } from '@trpc/server';export constt =initTRPC .create ();constpublicProcedure =t .procedure ;export constappRouter =t .router ({hello :publicProcedure .input ((value ): string => {if (typeofvalue === 'string') {returnvalue ;}throw newError ('Input is not a string');}).output ((value ): string => {if (typeofvalue === 'string') {returnvalue ;}throw newError ('Output is not a string');}).query ((opts ) => {const {input } =opts ;return `hello ${input }`;}),});export typeAppRouter = typeofappRouter ;
库集成方案
tRPC 开箱即支持多款主流验证解析库,包括所有符合 Standard Schema 的库。以下是我们官方维护的部分验证器使用示例:
使用 Zod
Zod 是我们的默认推荐方案,其强大的生态体系使其成为代码库多模块复用的理想选择。若无特殊偏好且需要满足未来扩展的强力库,Zod 是绝佳选择。
tsimport {initTRPC } from '@trpc/server';import {z } from 'zod';export constt =initTRPC .create ();constpublicProcedure =t .procedure ;export constappRouter =t .router ({hello :publicProcedure .input (z .object ({name :z .string (),}),).output (z .object ({greeting :z .string (),}),).query (({input }) => {return {greeting : `hello ${input .name }`,};}),});export typeAppRouter = typeofappRouter ;
tsimport {initTRPC } from '@trpc/server';import {z } from 'zod';export constt =initTRPC .create ();constpublicProcedure =t .procedure ;export constappRouter =t .router ({hello :publicProcedure .input (z .object ({name :z .string (),}),).output (z .object ({greeting :z .string (),}),).query (({input }) => {return {greeting : `hello ${input .name }`,};}),});export typeAppRouter = typeofappRouter ;
使用 Yup
tsimport {initTRPC } from '@trpc/server';import * asyup from 'yup';export constt =initTRPC .create ();constpublicProcedure =t .procedure ;export constappRouter =t .router ({hello :publicProcedure .input (yup .object ({name :yup .string ().required (),}),).output (yup .object ({greeting :yup .string ().required (),}),).query (({input }) => {return {greeting : `hello ${input .name }`,};}),});export typeAppRouter = typeofappRouter ;
tsimport {initTRPC } from '@trpc/server';import * asyup from 'yup';export constt =initTRPC .create ();constpublicProcedure =t .procedure ;export constappRouter =t .router ({hello :publicProcedure .input (yup .object ({name :yup .string ().required (),}),).output (yup .object ({greeting :yup .string ().required (),}),).query (({input }) => {return {greeting : `hello ${input .name }`,};}),});export typeAppRouter = typeofappRouter ;
使用 Superstruct
tsimport {initTRPC } from '@trpc/server';import {object ,string } from 'superstruct';export constt =initTRPC .create ();constpublicProcedure =t .procedure ;export constappRouter =t .router ({hello :publicProcedure .input (object ({name :string () })).output (object ({greeting :string () })).query (({input }) => {return {greeting : `hello ${input .name }`,};}),});export typeAppRouter = typeofappRouter ;
tsimport {initTRPC } from '@trpc/server';import {object ,string } from 'superstruct';export constt =initTRPC .create ();constpublicProcedure =t .procedure ;export constappRouter =t .router ({hello :publicProcedure .input (object ({name :string () })).output (object ({greeting :string () })).query (({input }) => {return {greeting : `hello ${input .name }`,};}),});export typeAppRouter = typeofappRouter ;
使用 scale-ts
tsimport {initTRPC } from '@trpc/server';import * as$ from 'scale-codec';export constt =initTRPC .create ();constpublicProcedure =t .procedure ;export constappRouter =t .router ({hello :publicProcedure .input ($ .object ($ .field ('name',$ .str ))).output ($ .object ($ .field ('greeting',$ .str ))).query (({input }) => {return {greeting : `hello ${input .name }`,};}),});export typeAppRouter = typeofappRouter ;
tsimport {initTRPC } from '@trpc/server';import * as$ from 'scale-codec';export constt =initTRPC .create ();constpublicProcedure =t .procedure ;export constappRouter =t .router ({hello :publicProcedure .input ($ .object ($ .field ('name',$ .str ))).output ($ .object ($ .field ('greeting',$ .str ))).query (({input }) => {return {greeting : `hello ${input .name }`,};}),});export typeAppRouter = typeofappRouter ;
使用 Typia
tsimport { 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;
tsimport { 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
tsimport { 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;
tsimport { 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
tsimport { 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;
tsimport { 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
tsimport {initTRPC } from '@trpc/server';import * asv from 'valibot';export constt =initTRPC .create ();constpublicProcedure =t .procedure ;export constappRouter =t .router ({hello :publicProcedure .input (v .object ({name :v .string () })).output (v .object ({greeting :v .string () })).query (({input }) => {return {greeting : `hello ${input .name }`,};}),});export typeAppRouter = typeofappRouter ;
tsimport {initTRPC } from '@trpc/server';import * asv from 'valibot';export constt =initTRPC .create ();constpublicProcedure =t .procedure ;export constappRouter =t .router ({hello :publicProcedure .input (v .object ({name :v .string () })).output (v .object ({greeting :v .string () })).query (({input }) => {return {greeting : `hello ${input .name }`,};}),});export typeAppRouter = typeofappRouter ;
使用 @robolex/sure
您可按需定义自定义错误类型及错误抛出函数。为简化操作,@robolex/sure 提供了 sure/src/err.ts:
ts// sure/src/err.tsexport const err = (schema) => (input) => {const [good, result] = schema(input);if (good) return result;throw result;};
ts// sure/src/err.tsexport const err = (schema) => (input) => {const [good, result] = schema(input);if (good) return result;throw result;};
tsimport { 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;
tsimport { 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
tsimport { 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;
tsimport { 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 讨论。具体支持的解析/验证接口和函数可查阅源码。