今天繼續介紹 trpc api 的部分~終於到第三天開始介紹主題了撒花!!!,今天主要教學 api 的封裝與 context 的共用。
RPC 架構中優一個很重要的概念就 procedures ,procedures 主要負責 api 的 request 到 response 的所有程序,同時他可以幫你處理不管是 middleware 或是 request authorization 等等,而每個 procedures 在 trpc 中都會先初始化一個 context。
//t1 就是一個 procedures
const t1 = initTRPC.context().create();
// t2 就是另外一個 procedures
const t2 = initTRPC.context().create();
首先你需要定義 api router ,在 t3 stack 中會是在 api route 中去呼叫 trpc,所以首先你需要先創建一個 handler 如下。
// /pages/api/trpc/[trpc]
import { createNextApiHandler } from "@trpc/server/adapters/next";
import { env } from "~/env.mjs";
import { appRouter } from "~/server/api/root";
import { createTRPCContext } from "~/server/api/trpc";
// export API handler
export default createNextApiHandler({
router: appRouter,
createContext: createTRPCContext,
onError:
env.NODE_ENV === "development"
? ({ path, error }) => {
console.error(
`❌ tRPC failed on ${path ?? "<no-path>"}: ${error.message}`
);
}
: undefined,
});
那 appRouter 跟 createTRPCContext 怎麼來的呢?沒關係我們往下走起
定義好 context 後,指定一個 public 的 procedure 日後可以重用。
const t = initTRPC.context().create({})
const publicProcedure = t.procedure;
createTRPCRouter 就是定義 route 邏輯的地方,可以想成 api 的 handler,這邊如果寫過 graphql 的話一定很熟悉, trpc 跟 graphql 一樣都是透過 query 去 return value喔~
import { z } from "zod";
const exampleRouter = createTRPCRouter({
hello: publicProcedure
.input(z.object({ text: z.string() }))
.query(({ input }) => {
return {
greeting: `Hello ${input.text}`,
};
})
})
hello : api route 的名稱。input : request 的 query 。query : 這邊的 callback function 可以吃到 你定義 input 得結果。
在 trpc 中跟 graphql 一樣是透過 mutate 去管理 api 的 post 、delete 、put
const posts = []
const exampleRouter = createTRPCRouter({
hello: publicProcedure
.input(z.object({ text: z.string() }))
.query(({ input }) => {
return {
greeting: `Hello ${input.text}`,
};
}),
add: publicProcedure
.input(z.string())
.mutation(({input}) => {
posts.push(opts.input);
return posts;
}),
})
這邊透過 posts 當作 db 資料
// 定義所有的 api route
export const appRouter = createTRPCRouter({
example: exampleRouter,
});
context 可以在 appRouter 中所有的 router 可以呼叫 context 內容在 query function 中。
import { type CreateNextContextOptions } from "@trpc/server/adapters/next";
import { PrismaClient } from "@prisma/client";
type CreateContextOptions = {
req: NextApiRequest,
res: NextApiResponse
};
const prisma = new PrismaClient()
const createInnerTRPCContext = (opts: CreateContextOptions) => {
return {
prisma,
res: opts.res,
req: opts.req
};
};
const createTRPCContext = async (opts: CreateNextContextOptions) => {
const { req, res } = opts;
// Get the session from the server using the getServerSession wrapper function
return createInnerTRPCContext({
req,
res
});
};
以 hello 這個 route 來說這裡的 ctx 就是透過 createTRPCContext 中 return 的 value,那因為會使用 prisma 這個框架,就會把他放到 context 中,這樣之後所有的 procedure ,都可以共用同一個 prisma instance,所以如果你希望用到其他的 ORM 框架的話只要放到 createTRPCContext 中就可以瞜~
hello: publicProcedure
.input(z.object({ text: z.string() }))
.query(({ input, ctx }) => {
const { req, res, prisma } = ctx
return {
greeting: `Hello ${input.text}`,
};
}),
完整的 demo
// 新增一個新的 trpc context
const t = initTRPC.context<typeof createTRPCContext>().create({})
const publicProcedure = t.procedure;
// 定義 api route
const exampleRouter = createTRPCRouter({
hello: publicProcedure
.input(z.object({ text: z.string() }))
.query(({ input }) => {
return {
greeting: `Hello ${input.text}`,
};
})
})
export const appRouter = createTRPCRouter({
example: exampleRouter,
});
export type AppRouter = typeof appRouter;
// ~/server/api/trpc
export default createNextApiHandler({
router: appRouter,
createContext: createTRPCContext,
onError:
env.NODE_ENV === "development"
? ({ path, error }) => {
console.error(
`❌ tRPC failed on ${path ?? "<no-path>"}: ${error.message}`
);
}
: undefined,
});
server 端定義好後,接下來就來介紹 clinet 端怎麼呼叫~,用法很簡單只要引用 AppRouter 到 createTRPCNext function 中就好了,這邊的 createTRPCNext 是封裝 api request 結果。
transformer : de-serialization data to server。loggerLink : loghttpBatchLink : 整合 request 結果例如 url 或是 header
import { type AppRouter } from "~/server/api/root";
export const api = createTRPCNext<AppRouter>({
config() {
return {
transformer: superjson,
links: [
loggerLink({
enabled: (opts) =>
process.env.NODE_ENV === "development" ||
(opts.direction === "down" && opts.result instanceof Error),
}),
httpBatchLink({
url: `${process.env.your_base_url}/api/trpc`,
headers: () => ({
Authorization: `Bearer ${your_token}`
})
}),
],
};
},
// client 端 call api 結果是否要在 server side render 呼叫 client 端做 cache
ssr: false,
});
是不是跟 react query 用起來很像呢,沒錯!!!trpc client 端就是封裝 react query ,所以所有 react query 的概念這邊都可以通用,看到這邊是不是要好好學好 react query 了呢 XD
const SomeComponent = ()=>{
const { data, isLoading, isError } = api.example.hello.useQuery({ text: "from tRPC" });
if(isLoading) return 'Loading...'
if(isError) return 'Error!!'
return (
<>{data.greeting}</> // Hello from tRPC
)
}
最後謝謝大家耐心看完今天內容,總共花了三天才開始進到 code,畢竟要把 trpc 這個概念解釋清楚一時間很難說的很完整,如果內容有錯誤或是不懂地方還請大家指正~如果有任何想法可以底下留言讓我知道喔XD
✅ 前端社群 :
https://lihi3.cc/kBe0Y
httpBatchLink : 整合 request 結果例如 url 或是 he`ader
這裡的 header 好像誤加了一個符號 !?
好嚴格的 code review