今天來介紹 Context
跟 middleware
在 trpc
中你可以透過 context
去放置一些 db connection
Authorization info
,透過 context
傳遞到所有 porcedures
中,或是一些你想管理的共用 function
你也可以隨你喜好添加,可以想像 context
就是後端的 state management
。
首先你需要訂一個 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
以下方 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
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