iT邦幫忙

2023 iThome 鐵人賽

DAY 1
0
Modern Web

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

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

  • 分享至 

  • xImage
  •  

今天來介紹 Contextmiddleware

Context

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

context type

首先你需要訂一個 createContext function,那這邊筆者習慣用 prisma 所以會在 createContextreturn 他,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,
});

之後我們定義 prismadb 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 拉~

於是我們再加一個 addPostsporcedures

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 交給 publicProcedureuse,這時你就會拿到有 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 一樣,如此以來就成功定義好一個 protectrouter

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) }}
直播中

尚未有邦友留言

立即登入留言