跳到主要內容
版本:11.x

輸入和輸出驗證器

tRPC 程序可以定義其輸入和/或輸出的驗證邏輯,驗證器也用於推斷輸入和輸出的類型。我們對許多熱門驗證器提供一流的支援,您可以整合驗證器,而我們不直接支援這些驗證器。

輸入驗證器

透過定義輸入驗證器,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 可立即與許多流行的驗證和剖析函式庫搭配使用。以下是我們正式維護支援的驗證器使用範例。

使用 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' }).assert)
.output(type({ greeting: 'string' }).assert)
.query(({ input }) => {
(parameter) input: { name: string; }
return {
greeting: `hello ${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' }).assert)
.output(type({ greeting: 'string' }).assert)
.query(({ input }) => {
(parameter) input: { name: string; }
return {
greeting: `hello ${input.name}`,
};
}),
});
 
export type AppRouter = typeof appRouter;

使用 runtypes

ts
import { 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 }) => {
(parameter) input: { name: string; }
return {
greeting: `hello ${input.name}`,
};
}),
});
 
export type AppRouter = typeof appRouter;
ts
import { 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 }) => {
(parameter) input: { name: string; }
return {
greeting: `hello ${input.name}`,
};
}),
});
 
export type AppRouter = typeof appRouter;

使用 Valibot

ts
import { initTRPC } from '@trpc/server';
import { wrap } from '@typeschema/valibot';
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 }) => {
(parameter) input: any
return {
greeting: `hello ${input.name}`,
};
}),
});
 
export type AppRouter = typeof appRouter;
ts
import { initTRPC } from '@trpc/server';
import { wrap } from '@typeschema/valibot';
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 }) => {
(parameter) input: any
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;

提供您自己的驗證程式庫

如果您使用支援 tRPC 用法的驗證程式庫,歡迎為此頁面開啟 PR,提供與其他範例等效的用法,並附上您的文件連結。

在大部分情況下,與 tRPC 整合只要符合幾個現有的類型介面即可,但在某些情況下,我們可能會接受 PR 以新增支援的新介面。歡迎開啟議題進行討論。您可以在這裡查看現有的支援介面