クイックスタート
このページは PageTurner AI で翻訳されました(ベータ版)。プロジェクト公式の承認はありません。 エラーを見つけましたか? 問題を報告 →
tRPCはRESTとGraphQLの概念を組み合わせています。どちらにも慣れていない場合は、主要な概念を参照してください。
インストール
tRPCは複数のパッケージに分割されているため、必要なものだけをインストールできます。パッケージはコードベースの適切なセクションにインストールしてください。このクイックスタートガイドではシンプルに保つため、バニラクライアントのみを使用します。フレームワーク別ガイドについては、Reactでの使用方法とNext.jsでの使用方法を確認してください。
- tRPCにはTypeScript >= 4.7.0が必要です
- 公式に非strictモードをサポートしていないため、
tsconfig.jsonで"strict": trueの使用を強く推奨します
まず、@trpc/serverと@trpc/clientパッケージをインストールします:
- npm
- yarn
- pnpm
- bun
shnpm install @trpc/server @trpc/client
shnpm install @trpc/server @trpc/client
shyarn add @trpc/server @trpc/client
shyarn add @trpc/server @trpc/client
shpnpm add @trpc/server @trpc/client
shpnpm add @trpc/server @trpc/client
shbun add @trpc/server @trpc/client
shbun add @trpc/server @trpc/client
バックエンドルーターの定義
tRPCで型安全なAPIを構築する手順を見ていきましょう。最初に、このAPIには以下のTypeScriptシグネチャを持つ3つのエンドポイントが含まれます:
tstype User = { id: string; name: string; };userList: () => User[];userById: (id: string) => User;userCreate: (data: { name: string }) => User;
tstype User = { id: string; name: string; };userList: () => User[];userById: (id: string) => User;userCreate: (data: { name: string }) => User;
1. ルーターインスタンスの作成
まずtRPCバックエンドを初期化します。tRPCオブジェクト全体ではなく、再利用可能なヘルパー関数をエクスポートするために、別ファイルで行うのが良い慣習です。
server/trpc.tstsimport {initTRPC } from '@trpc/server';/*** Initialization of tRPC backend* Should be done only once per backend!*/constt =initTRPC .create ();/*** Export reusable router and procedure helpers* that can be used throughout the router*/export constrouter =t .router ;export constpublicProcedure =t .procedure ;
server/trpc.tstsimport {initTRPC } from '@trpc/server';/*** Initialization of tRPC backend* Should be done only once per backend!*/constt =initTRPC .create ();/*** Export reusable router and procedure helpers* that can be used throughout the router*/export constrouter =t .router ;export constpublicProcedure =t .procedure ;
次にメインルーターインスタンス(通常appRouterと呼びます)を初期化し、後でプロシージャを追加します。最後に、クライアント側で後ほど使用するためにルーターの型をエクスポートする必要があります。
server/index.tstsimport {router } from './trpc';constappRouter =router ({// ...});// Export type router type signature,// NOT the router itself.export typeAppRouter = typeofappRouter ;
server/index.tstsimport {router } from './trpc';constappRouter =router ({// ...});// Export type router type signature,// NOT the router itself.export typeAppRouter = typeofappRouter ;
2. クエリプロシージャの追加
ルーターにクエリプロシージャを追加するにはpublicProcedure.query()を使用します。
以下はuserListというクエリプロシージャを作成し、データベースからユーザーリストを返します:
server/index.tstsimport {db } from './db';import {publicProcedure ,router } from './trpc';constappRouter =router ({userList :publicProcedure .query (async () => {// Retrieve users from a datasource, this is an imaginary databaseconstusers = awaitdb .user .findMany ();returnusers ;}),});
server/index.tstsimport {db } from './db';import {publicProcedure ,router } from './trpc';constappRouter =router ({userList :publicProcedure .query (async () => {// Retrieve users from a datasource, this is an imaginary databaseconstusers = awaitdb .user .findMany ();returnusers ;}),});
3. 入力パーサーを使用したプロシージャ入力の検証
userByIdプロシージャを実装するには、クライアントからの入力を受け入れる必要があります。tRPCでは入力パーサーを定義して入力を検証・解析できます。独自の入力パーサーを定義するか、zod、yup、superstructなどの検証ライブラリを使用できます。
入力パーサーはpublicProcedure.input()で定義し、下記のようにリゾルバー関数からアクセスできます:
- Vanilla
- Zod
- Yup
- Valibot
The input parser should be a function that validates and casts the input of this procedure. It should return a strongly typed value when the input is valid or throw an error if the input is invalid.
server/index.tstsconstappRouter =router ({// ...userById :publicProcedure // The input is unknown at this time. A client could have sent// us anything so we won't assume a certain data type..input ((val : unknown) => {// If the value is of type string, return it.// It will now be inferred as a string.if (typeofval === 'string') returnval ;// Uh oh, looks like that input wasn't a string.// We will throw an error instead of running the procedure.throw newError (`Invalid input: ${typeofval }`);}).query (async (opts ) => {const {input } =opts ;// Retrieve the user with the given IDconstuser = awaitdb .user .findById (input );returnuser ;}),});
server/index.tstsconstappRouter =router ({// ...userById :publicProcedure // The input is unknown at this time. A client could have sent// us anything so we won't assume a certain data type..input ((val : unknown) => {// If the value is of type string, return it.// It will now be inferred as a string.if (typeofval === 'string') returnval ;// Uh oh, looks like that input wasn't a string.// We will throw an error instead of running the procedure.throw newError (`Invalid input: ${typeofval }`);}).query (async (opts ) => {const {input } =opts ;// Retrieve the user with the given IDconstuser = awaitdb .user .findById (input );returnuser ;}),});
The input parser can be any ZodType, e.g. z.string() or z.object().
server.tstsimport {z } from 'zod';import {db } from './db';import {publicProcedure ,router } from './trpc';constappRouter =router ({// ...userById :publicProcedure .input (z .string ()).query (async (opts ) => {const {input } =opts ;// Retrieve the user with the given IDconstuser = awaitdb .user .findById (input );returnuser ;}),});
server.tstsimport {z } from 'zod';import {db } from './db';import {publicProcedure ,router } from './trpc';constappRouter =router ({// ...userById :publicProcedure .input (z .string ()).query (async (opts ) => {const {input } =opts ;// Retrieve the user with the given IDconstuser = awaitdb .user .findById (input );returnuser ;}),});
The input parser can be any YupSchema, e.g. yup.string() or yup.object().
server.tstsimport * asyup from 'yup';import {db } from './db';import {publicProcedure ,router } from './trpc';constappRouter =router ({// ...userById :publicProcedure .input (yup .string ().required ()).query (async (opts ) => {const {input } =opts ;// Retrieve the user with the given IDconstuser = awaitdb .user .findById (input );returnuser ;}),});
server.tstsimport * asyup from 'yup';import {db } from './db';import {publicProcedure ,router } from './trpc';constappRouter =router ({// ...userById :publicProcedure .input (yup .string ().required ()).query (async (opts ) => {const {input } =opts ;// Retrieve the user with the given IDconstuser = awaitdb .user .findById (input );returnuser ;}),});
With Valibot, you simply need to wrap your schema with TypeSchema.
server.tsts// @filename: trpc.ts// @include: trpc// @filename: db.ts// @include: db// @filename: server.ts// ---cut---import { wrap } from '@decs/typeschema';import { string } from 'valibot';import { db } from './db';import { publicProcedure, router } from './trpc';const appRouter = router({// ...userById: publicProcedure.input(wrap(string())).query(async (opts) => {const { input } = opts;// ^?// Retrieve the user with the given IDconst user = await db.user.findById(input);// ^?return user;}),});
server.tsts// @filename: trpc.ts// @include: trpc// @filename: db.ts// @include: db// @filename: server.ts// ---cut---import { wrap } from '@decs/typeschema';import { string } from 'valibot';import { db } from './db';import { publicProcedure, router } from './trpc';const appRouter = router({// ...userById: publicProcedure.input(wrap(string())).query(async (opts) => {const { input } = opts;// ^?// Retrieve the user with the given IDconst user = await db.user.findById(input);// ^?return user;}),});
このドキュメントの残りの部分では、検証ライブラリとしてzodを使用します。
4. ミューテーションプロシージャの追加
GraphQLと同様に、tRPCはクエリプロシージャとミューテーションプロシージャを区別します。
サーバー上のプロシージャの動作は、クエリとミューテーションで大きな違いはありません。メソッド名が異なり、クライアントがこのプロシージャを使用する方法が変わるだけです。それ以外はすべて同じです!
ルーターオブジェクトに新しいプロパティとしてuserCreateミューテーションを追加しましょう:
server.tstsconstappRouter =router ({// ...userCreate :publicProcedure .input (z .object ({name :z .string () })).mutation (async (opts ) => {const {input } =opts ;// Create a new user in the databaseconstuser = awaitdb .user .create (input );returnuser ;}),});
server.tstsconstappRouter =router ({// ...userCreate :publicProcedure .input (z .object ({name :z .string () })).mutation (async (opts ) => {const {input } =opts ;// Create a new user in the databaseconstuser = awaitdb .user .create (input );returnuser ;}),});
APIの提供
ルーターを定義したので、提供できます。tRPCには多くのアダプターがあるため、任意のバックエンドフレームワークを使用できます。シンプルにするために、standaloneアダプターを使用します。
server/index.tstsimport {createHTTPServer } from '@trpc/server/adapters/standalone';import {router } from './trpc';constappRouter =router ({// ...});constserver =createHTTPServer ({router :appRouter ,});server .listen (3000);
server/index.tstsimport {createHTTPServer } from '@trpc/server/adapters/standalone';import {router } from './trpc';constappRouter =router ({// ...});constserver =createHTTPServer ({router :appRouter ,});server .listen (3000);
See the full backend code
server/db.tststypeUser = {id : string;name : string };// Imaginary databaseconstusers :User [] = [];export constdb = {user : {findMany : async () =>users ,findById : async (id : string) =>users .find ((user ) =>user .id ===id ),create : async (data : {name : string }) => {constuser = {id :String (users .length + 1), ...data };users .push (user );returnuser ;},},};
server/db.tststypeUser = {id : string;name : string };// Imaginary databaseconstusers :User [] = [];export constdb = {user : {findMany : async () =>users ,findById : async (id : string) =>users .find ((user ) =>user .id ===id ),create : async (data : {name : string }) => {constuser = {id :String (users .length + 1), ...data };users .push (user );returnuser ;},},};
server/trpc.tstsimport {initTRPC } from '@trpc/server';constt =initTRPC .create ();export constrouter =t .router ;export constpublicProcedure =t .procedure ;
server/trpc.tstsimport {initTRPC } from '@trpc/server';constt =initTRPC .create ();export constrouter =t .router ;export constpublicProcedure =t .procedure ;
server/index.tstsimport {createHTTPServer } from "@trpc/server/adapters/standalone";import {z } from "zod";import {db } from "./db";import {publicProcedure ,router } from "./trpc";constappRouter =router ({userList :publicProcedure .query (async () => {constusers = awaitdb .user .findMany ();returnusers ;}),userById :publicProcedure .input (z .string ()).query (async (opts ) => {const {input } =opts ;constuser = awaitdb .user .findById (input );returnuser ;}),userCreate :publicProcedure .input (z .object ({name :z .string () })).mutation (async (opts ) => {const {input } =opts ;constuser = awaitdb .user .create (input );returnuser ;}),});export typeAppRouter = typeofappRouter ;constserver =createHTTPServer ({router :appRouter ,});server .listen (3000);
server/index.tstsimport {createHTTPServer } from "@trpc/server/adapters/standalone";import {z } from "zod";import {db } from "./db";import {publicProcedure ,router } from "./trpc";constappRouter =router ({userList :publicProcedure .query (async () => {constusers = awaitdb .user .findMany ();returnusers ;}),userById :publicProcedure .input (z .string ()).query (async (opts ) => {const {input } =opts ;constuser = awaitdb .user .findById (input );returnuser ;}),userCreate :publicProcedure .input (z .object ({name :z .string () })).mutation (async (opts ) => {const {input } =opts ;constuser = awaitdb .user .create (input );returnuser ;}),});export typeAppRouter = typeofappRouter ;constserver =createHTTPServer ({router :appRouter ,});server .listen (3000);
クライアント側での新しいバックエンドの使用
クライアントサイドのコードに移り、エンドツーエンドのタイプセーフティの力を実感しましょう。クライアントで使用するためにAppRouter型をインポートするだけで、実装の詳細をクライアントに漏らすことなくシステム全体の完全なタイプセーフティを実現できます。
1. tRPCクライアントのセットアップ
client/index.tstsimport {createTRPCProxyClient ,httpBatchLink } from '@trpc/client';import type {AppRouter } from './server';// 👆 **type-only** import// Pass AppRouter as generic here. 👇 This lets the `trpc` object know// what procedures are available on the server and their input/output types.consttrpc =createTRPCProxyClient <AppRouter >({links : [httpBatchLink ({url : 'http://localhost:3000',}),],});
client/index.tstsimport {createTRPCProxyClient ,httpBatchLink } from '@trpc/client';import type {AppRouter } from './server';// 👆 **type-only** import// Pass AppRouter as generic here. 👇 This lets the `trpc` object know// what procedures are available on the server and their input/output types.consttrpc =createTRPCProxyClient <AppRouter >({links : [httpBatchLink ({url : 'http://localhost:3000',}),],});
tRPCのリンクはGraphQLのリンクと似ており、データがサーバーに送信される前にデータフローを制御できます。上記の例ではhttpBatchLinkを使用しており、複数の呼び出しを単一のHTTPリクエストに自動的にまとめます。リンクのより詳細な使い方については、リンクのドキュメントを参照してください。
2. クエリとミューテーションの実行
これでtrpcオブジェクトを通じてAPIプロシージャにアクセスできます。実際に試してみましょう!
client/index.tsts// Inferred typesconstuser = awaittrpc .userById .query ('1');constcreatedUser = awaittrpc .userCreate .mutate ({name : 'sachinraja' });
client/index.tsts// Inferred typesconstuser = awaittrpc .userById .query ('1');constcreatedUser = awaittrpc .userCreate .mutate ({name : 'sachinraja' });
完全なオートコンプリート
Intellisenseを開くと、フロントエンドでAPIを探索できます。すべてのプロシージャルートとそれらを呼び出すメソッドが用意されているのが確認できます。
client/index.tsts// Full autocompletion on your routestrpc .u ;
client/index.tsts// Full autocompletion on your routestrpc .u ;
実際に試してみましょう!
次のステップ
お気に入りのフレームワークでtRPCをどのようにインストールするか学ぶには、サンプルアプリを確認することを強くお勧めします。
デフォルトでは、tRPCはDateのような複雑な型をJSON互換の形式(Dateの場合はstring)にマッピングします。これらの型の整合性を保持したい場合は、superjsonを使用するのがデータトランスフォーマーとして最も簡単な方法です。
tRPCにはReactプロジェクトやNext.js向けに設計された、より高度なクライアントサイドツールが含まれています。