入力と出力のバリデーター
tRPC プロシージャは、入力や出力のバリデーションロジックを定義でき、バリデーターは入力と出力の型を推論するためにも使用されます。多くの一般的なバリデーターをファーストクラスでサポートしており、直接サポートしていないバリデーターを統合できます。
入力バリデーター
入力バリデーターを定義することで、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()
は、より複雑な型を構築するためにスタックできます。これは、ミドルウェアでプロシージャのコレクションへの共通の入力を利用したい場合に特に役立ちます。
ts
constbaseProcedure =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 }`,};}),});
ts
constbaseProcedure =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
で応答します。
ts
import {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 ,};}),});
ts
import {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です!
ほとんどの場合、バリデーションライブラリを使用することをお勧めします
ts
import {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 ;
ts
import {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 は、多くの一般的なバリデーションおよび解析ライブラリとすぐに使用できます。以下は、公式にサポートを維持しているバリデーターを使用した使用例です。
Zod の場合
Zod は、私たちのデフォルトの推奨事項です。強力なエコシステムを備えているため、コードベースの複数の部分で使用するのに最適です。独自の意見がなく、将来のニーズを制限しない強力なライブラリが必要な場合は、Zod が最適です。
ts
import {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 ;
ts
import {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 の場合
ts
import {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 ;
ts
import {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 の場合
ts
import {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 ;
ts
import {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 の場合
ts
import {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 ;
ts
import {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 の場合
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 constt =initTRPC .create ();constpublicProcedure =t .procedure ;export constappRouter =t .router ({hello :publicProcedure .input (type ({name : 'string' }).assert ).output (type ({greeting : 'string' }).assert ).query (({input }) => {return {greeting : `hello ${input .name }`,};}),});export typeAppRouter = typeofappRouter ;
ts
import {initTRPC } from '@trpc/server';import {type } from 'arktype';export constt =initTRPC .create ();constpublicProcedure =t .procedure ;export constappRouter =t .router ({hello :publicProcedure .input (type ({name : 'string' }).assert ).output (type ({greeting : 'string' }).assert ).query (({input }) => {return {greeting : `hello ${input .name }`,};}),});export typeAppRouter = typeofappRouter ;
runtypes の場合
ts
import {initTRPC } from '@trpc/server';import * asT from 'runtypes';constt =initTRPC .create ();constpublicProcedure =t .procedure ;export constappRouter =t .router ({hello :publicProcedure .input (T .Record ({name :T .String })).output (T .Record ({greeting :T .String })).query (({input }) => {return {greeting : `hello ${input .name }`,};}),});export typeAppRouter = typeofappRouter ;
ts
import {initTRPC } from '@trpc/server';import * asT from 'runtypes';constt =initTRPC .create ();constpublicProcedure =t .procedure ;export constappRouter =t .router ({hello :publicProcedure .input (T .Record ({name :T .String })).output (T .Record ({greeting :T .String })).query (({input }) => {return {greeting : `hello ${input .name }`,};}),});export typeAppRouter = typeofappRouter ;
Valibot の場合
ts
import {initTRPC } from '@trpc/server';import {wrap } from '@typeschema/valibot';import {object ,string } from 'valibot';export constt =initTRPC .create ();constpublicProcedure =t .procedure ;export constappRouter =t .router ({hello :publicProcedure .input (wrap (object ({name :string () }))).output (wrap (object ({greeting :string () }))).query (({input }) => {return {greeting : `hello ${input .name }`,};}),});export typeAppRouter = typeofappRouter ;
ts
import {initTRPC } from '@trpc/server';import {wrap } from '@typeschema/valibot';import {object ,string } from 'valibot';export constt =initTRPC .create ();constpublicProcedure =t .procedure ;export constappRouter =t .router ({hello :publicProcedure .input (wrap (object ({name :string () }))).output (wrap (object ({greeting :string () }))).query (({input }) => {return {greeting : `hello ${input .name }`,};}),});export typeAppRouter = typeofappRouter ;
@robolex/sure の場合
必要に応じて、独自の Error 型とエラーをスローする関数を定義できます。便宜上、@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;};
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 を受け入れる場合があります。議論のために問題を提起してください。既存のサポートされているインターフェイスはこちらで確認できます