输入输出验证器
非官方测试版翻译
本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →
tRPC 过程可为其输入和/或输出定义验证逻辑,验证器也用于推断输入输出的类型。我们为众多流行验证器提供一等支持,您也可以集成验证器来解决我们未直接支持的场景。
输入验证器
通过定义输入验证器,tRPC 可检查过程调用是否正确,并在出现错误时返回验证失败信息。
使用 procedure.input() 方法设置输入验证器:
ts// @target: esnextimport { initTRPC } from '@trpc/server';// ---cut---// Our examples use Zod by default, but usage with other libraries is identicalimport { 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;// ^?return {greeting: `Hello ${opts.input.name}`,};}),});
ts// @target: esnextimport { initTRPC } from '@trpc/server';// ---cut---// Our examples use Zod by default, but usage with other libraries is identicalimport { 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;// ^?return {greeting: `Hello ${opts.input.name}`,};}),});
输入合并
.input() 可堆叠使用以构建更复杂的类型,这在需要为中间件中的过程集合复用公共输入时特别有用。
ts// @target: esnextimport { initTRPC, TRPCError } from '@trpc/server';import { z } from 'zod';export const t = initTRPC.create();// ---cut---const baseProcedure = t.procedure.input(z.object({ townName: z.string() })).use((opts) => {const input = opts.input;// ^?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;// ^?return {greeting: `Hello ${input.name}, my friend from ${input.townName}`,};}),});
ts// @target: esnextimport { initTRPC, TRPCError } from '@trpc/server';import { z } from 'zod';export const t = initTRPC.create();// ---cut---const baseProcedure = t.procedure.input(z.object({ townName: z.string() })).use((opts) => {const input = opts.input;// ^?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;// ^?return {greeting: `Hello ${input.name}, my friend from ${input.townName}`,};}),});
输出验证器
由于 tRPC 通过推断过程返回类型自动提供类型安全,输出验证通常不如输入验证重要。定义输出验证器的典型场景包括:
-
验证来自不可信源的数据是否正确
-
确保不会向客户端返回多余数据
信息
若输出验证失败,服务器将返回 INTERNAL_SERVER_ERROR 错误。
ts// @target: esnextimport { initTRPC } from '@trpc/server';// @noErrors// ---cut---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// @target: esnextimport { initTRPC } from '@trpc/server';// @noErrors// ---cut---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!
多数情况下建议使用验证库。
tsimport { 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;// ^?return `hello ${input}`;}),});export type AppRouter = typeof appRouter;
tsimport { 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;// ^?return `hello ${input}`;}),});export type AppRouter = typeof appRouter;
库集成方案
tRPC 开箱即支持多种流行验证解析库。以下是我们官方维护的部分验证器使用示例。
使用 Zod
Zod 是我们的默认推荐方案,其强大的生态体系使其成为代码库多模块复用的理想选择。若无特殊偏好且需要满足未来扩展的强力库,Zod 是绝佳选择。
tsimport { 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 }) => {// ^?return {greeting: `hello ${input.name}`,};}),});export type AppRouter = typeof appRouter;
tsimport { 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 }) => {// ^?return {greeting: `hello ${input.name}`,};}),});export type AppRouter = typeof appRouter;
使用 Yup
tsimport { 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 }) => {// ^?return {greeting: `hello ${input.name}`,};}),});export type AppRouter = typeof appRouter;
tsimport { 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 }) => {// ^?return {greeting: `hello ${input.name}`,};}),});export type AppRouter = typeof appRouter;
使用 Superstruct
tsimport { 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 }) => {// ^?return {greeting: `hello ${input.name}`,};}),});export type AppRouter = typeof appRouter;
tsimport { 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 }) => {// ^?return {greeting: `hello ${input.name}`,};}),});export type AppRouter = typeof appRouter;
使用 scale-ts
tsimport { 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 }) => {// ^?return {greeting: `hello ${input.name}`,};}),});export type AppRouter = typeof appRouter;
tsimport { 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 }) => {// ^?return {greeting: `hello ${input.name}`,};}),});export type AppRouter = typeof appRouter;
使用 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' }).assert).output(type({ greeting: 'string' }).assert).query(({ input }) => {// ^?return {greeting: `hello ${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' }).assert).output(type({ greeting: 'string' }).assert).query(({ input }) => {// ^?return {greeting: `hello ${input.name}`,};}),});export type AppRouter = typeof appRouter;
使用 @effect/schema
tsimport * as Schema from '@effect/schema/Schema';import { initTRPC } from '@trpc/server';export const t = initTRPC.create();const publicProcedure = t.procedure;export const appRouter = t.router({hello: publicProcedure.input(Schema.decodeUnknownSync(Schema.Struct({ name: Schema.String }))).output(Schema.decodeUnknownSync(Schema.Struct({ greeting: Schema.String })),).query(({ input }) => {// ^?return {greeting: `hello ${input.name}`,};}),});export type AppRouter = typeof appRouter;
tsimport * as Schema from '@effect/schema/Schema';import { initTRPC } from '@trpc/server';export const t = initTRPC.create();const publicProcedure = t.procedure;export const appRouter = t.router({hello: publicProcedure.input(Schema.decodeUnknownSync(Schema.Struct({ name: Schema.String }))).output(Schema.decodeUnknownSync(Schema.Struct({ greeting: Schema.String })),).query(({ input }) => {// ^?return {greeting: `hello ${input.name}`,};}),});export type AppRouter = typeof appRouter;
使用 runtypes
tsimport { initTRPC } from '@trpc/server';import * as T from 'runtypes';const t = initTRPC.create();const publicProcedure = t.procedure;export const appRouter = t.router({hello: publicProcedure.input(T.Record({ name: T.String })).output(T.Record({ greeting: T.String })).query(({ input }) => {// ^?return {greeting: `hello ${input.name}`,};}),});export type AppRouter = typeof appRouter;
tsimport { initTRPC } from '@trpc/server';import * as T from 'runtypes';const t = initTRPC.create();const publicProcedure = t.procedure;export const appRouter = t.router({hello: publicProcedure.input(T.Record({ name: T.String })).output(T.Record({ greeting: T.String })).query(({ input }) => {// ^?return {greeting: `hello ${input.name}`,};}),});export type AppRouter = typeof appRouter;
使用 Valibot
tsimport { wrap } from '@decs/typeschema';import { initTRPC } from '@trpc/server';import { object, string } from 'valibot';export const t = initTRPC.create();const publicProcedure = t.procedure;export const appRouter = t.router({hello: publicProcedure.input(wrap(object({ name: string() }))).output(wrap(object({ greeting: string() }))).query(({ input }) => {// ^?return {greeting: `hello ${input.name}`,};}),});export type AppRouter = typeof appRouter;
tsimport { wrap } from '@decs/typeschema';import { initTRPC } from '@trpc/server';import { object, string } from 'valibot';export const t = initTRPC.create();const publicProcedure = t.procedure;export const appRouter = t.router({hello: publicProcedure.input(wrap(object({ name: string() }))).output(wrap(object({ greeting: string() }))).query(({ input }) => {// ^?return {greeting: `hello ${input.name}`,};}),});export type AppRouter = typeof appRouter;
贡献自定义验证库
若您维护支持 tRPC 的验证库,欢迎提交 PR 补充本页面的等效用法示例并附上文档链接。
与 tRPC 的集成通常只需符合现有类型接口,特殊情况下我们也会接受新增接口的 PR。欢迎创建 issue 讨论。现有支持接口可在此查阅: