快速入門
tRPC 結合了 REST 和 GraphQL 的概念。如果您不熟悉其中任何一種,請查看關鍵 概念。
安裝
tRPC 分散在多個套件中,因此您只能安裝您需要的部分。請務必在程式碼庫的適當區段中安裝您想要的套件。對於這個快速入門指南,我們將保持簡單,僅使用純粹的用戶端。對於架構指南,請查看 與 React 搭配使用 和 與 Next.js 搭配使用。
- tRPC 需要 TypeScript >= 4.7.0
- 我們強烈建議您在
tsconfig.json
中使用"strict": true
,因為我們不正式支援非嚴格模式。
首先安裝 @trpc/server
和 @trpc/client
套件
- npm
- yarn
- pnpm
- bun
npm install @trpc/server@next @trpc/client@next
yarn add @trpc/server@next @trpc/client@next
pnpm add @trpc/server@next @trpc/client@next
bun add @trpc/server@next @trpc/client@next
定義後端路由器
讓我們逐步了解如何使用 tRPC 建立類型安全的 API。首先,此 API 將包含三個具有這些 TypeScript 簽章的端點
ts
type User = { id: string; name: string; };userList: () => User[];userById: (id: string) => User;userCreate: (data: { name: string }) => User;
ts
type User = { id: string; name: string; };userList: () => User[];userById: (id: string) => User;userCreate: (data: { name: string }) => User;
1. 建立路由器實例
首先,讓我們初始化 tRPC 後端。建議在獨立檔案中執行此操作,並匯出可重複使用的輔助函式,而非整個 tRPC 物件。
server/trpc.tsts
import {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.tsts
import {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.tsts
import {router } from './trpc';constappRouter =router ({// ...});// Export type router type signature,// NOT the router itself.export typeAppRouter = typeofappRouter ;
server/index.tsts
import {router } from './trpc';constappRouter =router ({// ...});// Export type router type signature,// NOT the router itself.export typeAppRouter = typeofappRouter ;
2. 新增查詢程序
使用 publicProcedure.query()
將查詢程序新增至路由器。
以下建立一個名為 userList
的查詢程序,用於從我們的資料庫中傳回使用者清單
server/index.tsts
import {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.tsts
import {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
server/index.tsts
constappRouter =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.tsts
constappRouter =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 ;}),});
ZodType
,例如 z.string()
或 z.object()
。server.tsts
import {z } from 'zod';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.tsts
import {z } from 'zod';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 ;}),});
YupSchema
,例如 yup.string()
或 yup.object()
。server.tsts
import * asyup from 'yup';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.tsts
import * asyup from 'yup';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.tsts
import {wrap } from '@typeschema/valibot';import {string } from 'valibot';constappRouter =router ({// ...userById :publicProcedure .input (wrap (string ())).query (async (opts ) => {const {input } =opts ;// Retrieve the user with the given IDconstuser = awaitdb .user .findById (input );returnuser ;}),});
server.tsts
import {wrap } from '@typeschema/valibot';import {string } from 'valibot';constappRouter =router ({// ...userById :publicProcedure .input (wrap (string ())).query (async (opts ) => {const {input } =opts ;// Retrieve the user with the given IDconstuser = awaitdb .user .findById (input );returnuser ;}),});
在本文檔的其餘部分,我們將使用 zod
作為我們的驗證程式庫。
4. 新增變異程序
類似於 GraphQL,tRPC 區分查詢和變異程序。
程序在伺服器上的運作方式在查詢和變異之間沒有太大的變化。方法名稱不同,而且客戶端使用此程序的方式也會改變 - 但其他一切都是相同的!
讓我們透過將 userCreate
變異新增為路由器物件上的新屬性來新增它
server.tsts
constappRouter =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.tsts
constappRouter =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.tsts
import {createHTTPServer } from '@trpc/server/adapters/standalone';constappRouter =router ({// ...});constserver =createHTTPServer ({router :appRouter ,});server .listen (3000);
server/index.tsts
import {createHTTPServer } from '@trpc/server/adapters/standalone';constappRouter =router ({// ...});constserver =createHTTPServer ({router :appRouter ,});server .listen (3000);
查看完整的後端程式碼
server/db.tsts
typeUser = {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.tsts
typeUser = {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.tsts
import {initTRPC } from '@trpc/server';constt =initTRPC .create ();export constrouter =t .router ;export constpublicProcedure =t .procedure ;
server/trpc.tsts
import {initTRPC } from '@trpc/server';constt =initTRPC .create ();export constrouter =t .router ;export constpublicProcedure =t .procedure ;
server/index.tsts
import {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.tsts
import {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.tsts
import {createTRPCClient ,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 =createTRPCClient <AppRouter >({links : [httpBatchLink ({url : 'https://127.0.0.1:3000',}),],});
client/index.tsts
import {createTRPCClient ,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 =createTRPCClient <AppRouter >({links : [httpBatchLink ({url : 'https://127.0.0.1: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。