メインコンテンツへスキップ
バージョン: 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/suresure/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を歓迎します。

tRPCとの統合は、既存の型インターフェースのいずれかに準拠するだけで実現できる場合がほとんどです。Standard Schemaへの準拠を推奨しますが、新しいインターフェースの追加提案についてPRを受け入れる場合もあります。議論のためにIssueを開いてください。既存のサポート対象インターフェースやパース/バリデーション関数はコード内で確認できます。