iT邦幫忙

2023 iThome 鐵人賽

DAY 1
0
Modern Web

一些讓你看來很強的全端- trcp 伴讀系列 第 4

Day-04. 一些讓你看來很強的全端 TRPC 伴讀 - T3 Stack 介紹(下)

  • 分享至 

  • xImage
  •  

今天繼續介紹 trpc api 的部分~終於到第三天開始介紹主題了撒花!!!,今天主要教學 api 的封裝與 context 的共用。

trpc context

RPC 架構中優一個很重要的概念就 proceduresprocedures 主要負責 apirequestresponse 的所有程序,同時他可以幫你處理不管是 middleware 或是 request authorization 等等,而每個 procedurestrpc 中都會先初始化一個 context

//t1 就是一個 procedures
const t1 = initTRPC.context().create();
// t2 就是另外一個 procedures
const t2 = initTRPC.context().create();

trpc (server)

首先你需要定義 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,
});

appRoutercreateTRPCContext 怎麼來的呢?沒關係我們往下走起

新增 context

定義好 context 後,指定一個 publicprocedure 日後可以重用。

const t = initTRPC.context().create({})
const publicProcedure = t.procedure;

定義 api

createTRPCRouter 就是定義 route 邏輯的地方,可以想成 apihandler,這邊如果寫過 graphql 的話一定很熟悉, trpcgraphql 一樣都是透過 queryreturn 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 去管理 apipostdeleteput

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

// 定義所有的 api route
export const appRouter = createTRPCRouter({
  example: exampleRouter,
});

createTRPCContext

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 就是透過 createTRPCContextreturnvalue,那因為會使用 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,
});

trpc (client)

server 端定義好後,接下來就來介紹 clinet 端怎麼呼叫~,用法很簡單只要引用 AppRoutercreateTRPCNext function 中就好了,這邊的 createTRPCNext 是封裝 api request 結果。

transformer : de-serialization data to server。
loggerLink : log
httpBatchLink : 整合 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,
});

call api

是不是跟 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


上一篇
Day-03. 一些讓你看來很強的全端 TRPC 伴讀 - T3 Stack 介紹(上)
下一篇
Day-05. 一些讓你看來很強的全端 TRPC 伴讀 -TRPC CONTEXT
系列文
一些讓你看來很強的全端- trcp 伴讀30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
Vita Ora
iT邦新手 4 級 ‧ 2023-09-19 00:50:26

httpBatchLink : 整合 request 結果例如 url 或是 he`ader
這裡的 header 好像誤加了一個符號 !?

好嚴格的 code review

我要留言

立即登入留言