接下來的幾天會開始介紹 trpc backend
跟 client端的使用
:
backend
部分:
client
部分:
額外部分:
本次 trpc
章節會以上面的 list
為主,筆者會盡量補齊 trpc
的全家桶給大家學習,如果讀者有什麼想了解的部分可以下方討論喔~本次的 trpc
會全部以 Nextjs
這個 framework
為主,trpc
他有提供大部分 nodejs
相關生態系給大家使用:
backend:
client:
大家可以根據喜愛的框架去做選擇,會這次會用 Nextjs
原因是 trpc
的主要生態系會是以 Nextjs
為主,所以為了帶給讀者更完整齊全的教學,再加上本人比較習慣 Nextjs
所以會選用他,然後文章大部分的範例都是從官網來的,筆者會以官網的 demo 為主慢慢做延伸。
開始前先起一個空白 next
專案
npx create-next-app@latest next_demo
$ npm install @trpc/server @trpc/client @trpc/react-query @trpc/next @tanstack/react-query zod
首先在先 src
中新增 api/root.ts
與 next page
。
src
├── pages
│ └── _app.tsx
└── server
└── api
└── trpc.ts
// _app.tsx
import { api } from '@/utils/api'
import type { AppProps } from 'next/app'
function MyApp({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />
}
export default MyApp;
所有 route
需需要透過 initTRPC.create()
去初始化,所有的 api handler
都是透過 t
這個 instance
創建出來的。
// ~/server/api/trpc.ts
import { initTRPC } from '@trpc/server';
// You can use any variable name you like.
// We use t to keep things simple.
const t = initTRPC.create();
export const router = t.router;
export const middleware = t.middleware;
export const publicProcedure = t.procedure;
新增 root.ts
用於管理所有的 route
src
├── pages
│ ├── _app.tsx
│ ├── api
│ │ └── trpc
│ │ └── [trpc].ts
│ └── index.tsx
├── server
└── api
├── root.ts
└── trpc.ts
以下的範例就是創建一個 greeting api
,query
就是你 api
的結果,這邊要注意個是記得加 AppRouter
這個 type
因為之後 client端會去使用到喔
~
// ~/server/api/root.ts
import * as trpc from '@trpc/server';
import { publicProcedure, router } from './trpc';
const appRouter = router({
greeting: publicProcedure.query(() => 'hello tRPC v10!'),
});
// Export only the type of a router!
// This prevents us from importing server code on the client.
export type AppRouter = typeof appRouter;
新增 ~/app/api/trpc/[trpc]/route.ts
file
src
├── pages
│ ├── _app.tsx
│ ├── api
│ │ └── trpc
│ │ └── [trpc].ts
│ └── index.tsx
├── server
└── api
├── root.ts
└── trpc.ts
因為 nextjs
有 app folder
跟 page folder
使用差異,但這邊筆者建議使用 page folder
為主, trpc
目前對於 server compnent
的完整度還不齊全,但還是簡單 demo
一下 寫法差異,這邊要注意一下不能兩者混用,這樣 next
會不知道他 api
要吃 page
的還是 app
的。
// ~/pages/api/trpc/[trpc].ts
import { createNextApiHandler } from '@trpc/server/adapters/next';
import { appRouter } from '@/server/api/root';
export default createNextApiHandler({
router: appRouter,
createContext: () => ({}),
});
// ~/app/api/trpc/[trpc]/route.ts
import { appRouter } from '@/server/api/root';
import { fetchRequestHandler } from '@trpc/server/adapters/fetch';
const handler = (req: Request) =>
fetchRequestHandler({
endpoint: '/api/trpc',
req,
router: appRouter,
createContext: () => ({})
});
export { handler as GET, handler as POST };
最後查看結果,恭喜你!!你成功做到人生第一隻用 trpc
做的 api
了!!有沒有找到人生第一次寫 hello world
的感動呢XD
最後需要添加 api instance
給 client
端去接,接著新增 utils/api.ts
檔案
src
├── pages
│ ├── _app.tsx
│ ├── api
│ │ └── trpc
│ │ └── [trpc].ts
│ └── index.tsx
├── server
│ └── api
│ ├── root.ts
│ └── trpc.ts
└── utils
└── api.ts
下面一一介紹 createTRPCNext
方法:
// utils/api.ts
import { AppRouter } from '@/server/api/root';
import { httpBatchLink, loggerLink } from '@trpc/client';
import { createTRPCNext } from '@trpc/next';
export const api = createTRPCNext<AppRouter>({
config(opts) {
return {
links: [
httpBatchLink({
/**
* If you want to use SSR, you need to use the server's full URL
* @link https://trpc.io/docs/ssr
**/
url: `http://localhost:3000`,
// You can pass any HTTP headers you wish here
async headers() {
return {
};
},
}),
],
};
},
/**
* @link https://trpc.io/docs/ssr
**/
ssr: false,
});
httpBatchLink
: trpc
是透過 https request
去請求連結到 trpc
的 procedure
,然後處去發 api route
邏輯,如上面定義的 route
。headers
: 這邊跟就是放你 req header
的地方,例如常見的 Authorization
你可以這樣寫。
httpBatchLink({
url: `http://localhost:3000`,
async headers() {
return {
Authorization: `Bearer ${token}`
};
}
}),
ssr
: 因為 trpc
預設會在 server
跑 getInitialProps
做 prefetch
,這樣會造成 api response
時間太長,解法有兩個在 header
加 cache-control
,或是把 ssr: false
,然後透過 ssr helper
手動添加特定頁面做 prefetch
,ssr helper
日後會跟大家介紹,這邊記得先加 ssr:false
。
詳細資料可以看這邊
最後記得在 _app.tsx
包 api.withTRPC
這樣 trpc
才能在 client
端呼叫喔~
//_app.tsx
import { api } from '@/utils/api'
import type { AppProps } from 'next/app'
function MyApp({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />
}
export default api.withTRPC(MyApp);
添加 index.tsx
src
├── pages
│ ├── _app.tsx
│ ├── api
│ │ └── trpc
│ │ └── [trpc].ts
│ └── index.tsx
├── server
│ └── api
│ ├── root.ts
│ └── trpc.ts
└── utils
└── api.ts
讀者看到這邊先恭喜你已經完成 client
端連結拉,那因為 client
端是封裝 react query
,所以使用起來跟 react query
一樣呦~
//
import { api } from "@/utils/api";
import { GetServerSideProps, InferGetServerSidePropsType } from "next";
import Head from "next/head";
import Link from "next/link";
export default function Home() {
const { data, isLoading, isError } = api.greeting.useQuery()
if (isLoading) return 'isLoading'
if (isError) return 'isError'
return (
<>
{data}
</>
);
}
trpc
可以透過 responseMeta
來設定 cache-control
的值,這邊簡單介紹下面 demo
用到的內容。
代表 1 天內的 response 都是返回 cache 資料,一天後重新發 request
代表 1s 內的 response 都是返回 cache 資料 , 1s 到 1 天內在背景更改 cache data,而不是透過 request update cache ,一天後才會重新發 request ,這是一個性能與即時性的平衡用法。
export default createNextApiHandler({
router: appRouter,
createContext: createTRPCContext,
responseMeta(opts) {
const { ctx, paths, errors, type } = opts
const allPublic = paths && paths.every(path => path.includes('public'))
const allOk = errors.length === 0
const isQuery = type === 'query'
if (
allPublic &&
allOk &&
isQuery
) {
const ONE_DAY_IN_SECONDS = 60 * 60 * 24
// max-age=60 * 60 * 24 代表 1 天內 內的 response 都是返回 cache 資料,一天後重新發 request
// max-age=1, stale-while-revalidate=60 * 60 * 24 代表 1s 內的 response 都是返回 cache 資料 , 1s - 1 天內 背景更改 cache data,而不是透過 request update cache ,一天後才會重新發 request ,這是一個性能與即時性的平衡用法。
return {
headers: {
'cache-control': `max-age=1, stale-while-revalidate=${ONE_DAY_IN_SECONDS}`,
}
}
}
return {}
}
});
github
: https://github.com/Danny101201/next_demo/tree/main
✅ 前端社群 :
https://lihi3.cc/kBe0Y