iT邦幫忙

2023 iThome 鐵人賽

DAY 1
0
Modern Web

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

Day-08. 一些讓你看來很強的全端 TRPC 伴讀 - Context / middleware

  • 分享至 

  • xImage
  •  

今天來介紹 Context 跟 middleware

Context

在 trpc 中你可以透過 context 去放置一些 db connection Authorization info,透過 context 傳遞到所有 porcedures 中,或是一些你想管理的共用 function 你也可以隨你喜好添加,可以想像 context 就是後端的 state management。

context type

首先你需要訂一個 createContext function,那這邊筆者習慣用 prisma 所以會在 createContext 中 return 他,prisma 如果不會的朋友也沒關係,明日會再給大家簡單教學,這邊主要是先給大家看觀念。

之後記得在 initTRPC call context() 同時記得把 createContext type 帶進去。

// ~server/api/trpc.ts
import { initTRPC, type inferAsyncReturnType } from '@trpc/server';
import type { CreateNextContextOptions } from '@trpc/server/adapters/next';

export const createContext = async (opts: CreateNextContextOptions) => {

 
  return {
     
  };
};

const t = initTRPC.context<typeof createTRPCContext>().create({
  errorFormatter(opts) {
    const { shape, error } = opts
    return {
      ...shape,
      data: {
        ...shape.data,
        zodError:
          error.code === 'BAD_REQUEST' && error.cause instanceof ZodError
            ? error.cause.flatten()
            : null
      }
    }
  }
});

上面只有定義 function,但還沒給 trpc 去引用,而 trpc call context 的地方如果是 next 的話會是在 api route 中如下:

// ~api/trpc/[trpc].ts
import { createNextApiHandler } from '@trpc/server/adapters/next';

import { appRouter } from '@/server/api/root';
import { createTRPCContext } from '@/server/api/trpc';

export default createNextApiHandler({
  router: appRouter,
  createContext: createTRPCContext,
});

之後我們定義 prisma 的 db connect,然後放到 createContext 中。

// ~server/db.ts

import { PrismaClient } from "@prisma/client";

const globalForPrisma = globalThis as unknown as {
  prisma: PrismaClient | undefined;
};

export const prisma =
  globalForPrisma.prisma ??
  new PrismaClient({
    log:
      process.env.NODE_ENV === "development" ? ["query", "error", "warn"] : ["error"],
  });

if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;

// ~server/api/trpc.ts
export const createTRPCContext = async (opts: CreateNextContextOptions) => {

  return {
    prisma
  };
};

接著我們查看之前的 greeting porcedures ,你會發現第二個參數 ctx 其實就是 createContext return 的內容,這樣我們就可以在所有 porcedures 中使用 prisma 拉~

於是我們再加一個 addPosts 的 porcedures

export const appRouter = router({
  
  //...
  getPosts: publicProcedure
    .query(async ({ ctx }) => {
      const { prisma } = ctx
      const posts = await prisma.post.findMany({})
      return posts
    }),
});

然後我們看一下 api 結果,成功 return 我們要的內容了~

middleware

另一個今日重點就是 middleware 以下方 demo 為例,我們定義個 interface Context,透過 t.middleware create middleware。

在 isAdmin 可以透過 context 內容驗證 isAdmin 如果沒有就 throw error,成功則把 context 內容傳下去,之後把 isAdmin 這支 middleware 交給 publicProcedure 去 use,這時你就會拿到有 auth 驗證的 adminProcedure,結合上面 createTRPCContext 概念我們先 mock 一個 user 。

export const createTRPCContext = async (opts: CreateNextContextOptions) => {
  return {
     // some mock data
    user:{
      id:1,
      isAdmin:true,
      name:'Danny'
    }
  };
};
const t = initTRPC.context<typeof createTRPCContext>().create();
export const middleware = t.middleware;
export const publicProcedure = t.procedure;
export const router = t.router;

const isAdmin = middleware(async (opts) => {
  const { ctx } = opts;
  if (!ctx.user?.isAdmin) {
    throw new Error('UNAUTHORIZED');
  }
  return opts.next({
    ctx: {
      user: ctx.user,
    },
  });
});

export const adminProcedure = publicProcedure.use(isAdmin);


而 adminProcedure 用法跟 publicProcedure 一樣,如此以來就成功定義好一個 protect 的 router 了

const adminRouter = router({
  foo: publicProcedure.query(() => 'bar'),
  howAmI: adminProcedure.query(({ ctx }) => ctx.user.name), // danny
});

Logging

同時你也可以做 logging

const loggerMiddleware = middleware(async (opts) => {
  const start = Date.now();
 
  const result = await opts.next();
 
  const durationMs = Date.now() - start;
  const meta = { path: opts.path, type: opts.type, durationMs };
 
  result.ok
    ? console.log('OK request timing:', meta)
    : console.error('Non-OK request timing', meta);
 
  return result;
});
 
export const loggedProcedure = publicProcedure.use(loggerMiddleware);

import { loggedProcedure, router } from './trpc';
 
export const appRouter = router({
  foo: loggedProcedure.query(() => 'bar'),
  abc: loggedProcedure.query(() => 'def'),
});

今天內容比較簡單希望讀者可以學習到 context 概念跟 middleware 精髓,筆者相信讀者們每個都比我優秀有興趣的朋友可以玩玩看~

相關連結:

https://trpc.io/docs/server/middlewares

✅ 前端社群 :
https://lihi3.cc/kBe0Y


上一篇
Day-07. 一些讓你看來很強的全端 TRPC 伴讀 - Input / Output Validate
下一篇
Day-09. 一些讓你看來很強的全端 TRPC 伴讀 - prisma
系列文
一些讓你看來很強的全端- trcp 伴讀 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言