脈絡
您的脈絡持有所有 tRPC 程序都能存取的資料,而且是放置資料庫連線或驗證資訊等內容的絕佳位置。
設定脈絡分為 2 個步驟,初始化期間定義類型,然後為每個請求建立執行時間脈絡。
定義脈絡類型
使用 initTRPC
初始化 tRPC 時,您應在呼叫 .create()
之前將 .context<TContext>()
傳遞至 initTRPC
建構函數。類型 TContext
可以從函數的傳回類型推論,或明確定義。
這將確保您的脈絡在程序和中間件中正確輸入類型。
ts
import {initTRPC } from '@trpc/server';import type {CreateNextContextOptions } from '@trpc/server/adapters/next';import {getSession } from 'next-auth/react';export constcreateContext = async (opts :CreateNextContextOptions ) => {constsession = awaitgetSession ({req :opts .req });return {session ,};};constt1 =initTRPC .context <typeofcreateContext >().create ();t1 .procedure .use (({ctx }) => { ... });typeContext =Awaited <ReturnType <typeofcreateContext >>;constt2 =initTRPC .context <Context >().create ();t2 .procedure .use (({ctx }) => { ... });
ts
import {initTRPC } from '@trpc/server';import type {CreateNextContextOptions } from '@trpc/server/adapters/next';import {getSession } from 'next-auth/react';export constcreateContext = async (opts :CreateNextContextOptions ) => {constsession = awaitgetSession ({req :opts .req });return {session ,};};constt1 =initTRPC .context <typeofcreateContext >().create ();t1 .procedure .use (({ctx }) => { ... });typeContext =Awaited <ReturnType <typeofcreateContext >>;constt2 =initTRPC .context <Context >().create ();t2 .procedure .use (({ctx }) => { ... });
建立脈絡
createContext()
函數必須傳遞至掛載 appRouter 的處理常式,這可能是透過 HTTP、伺服器端呼叫 或我們的 伺服器端輔助程式。
每次呼叫 tRPC 時,都會呼叫 createContext()
,因此批次要求會共用一個內容。
ts
// 1. HTTP requestimport { createHTTPHandler } from '@trpc/server/adapters/standalone';import { createContext } from './context';import { createCaller } from './router';const handler = createHTTPHandler({router: appRouter,createContext,});
ts
// 1. HTTP requestimport { createHTTPHandler } from '@trpc/server/adapters/standalone';import { createContext } from './context';import { createCaller } from './router';const handler = createHTTPHandler({router: appRouter,createContext,});
ts
// 2. Server-side callimport { createContext } from './context';import { createCaller } from './router';const caller = createCaller(await createContext());
ts
// 2. Server-side callimport { createContext } from './context';import { createCaller } from './router';const caller = createCaller(await createContext());
ts
// 3. servers-side helpersimport { createServerSideHelpers } from '@trpc/react-query/server';import { createContext } from './context';import { appRouter } from './router';const helpers = createServerSideHelpers({router: appRouter,ctx: await createContext(),});
ts
// 3. servers-side helpersimport { createServerSideHelpers } from '@trpc/react-query/server';import { createContext } from './context';import { appRouter } from './router';const helpers = createServerSideHelpers({router: appRouter,ctx: await createContext(),});
範例程式碼
tsx
// -------------------------------------------------// @filename: context.ts// -------------------------------------------------import type {CreateNextContextOptions } from '@trpc/server/adapters/next';import {getSession } from 'next-auth/react';/*** Creates context for an incoming request* @link https://trpc.dev.org.tw/docs/v11/context*/export async functioncreateContext (opts :CreateNextContextOptions ) {constsession = awaitgetSession ({req :opts .req });return {session ,};}export typeContext =Awaited <ReturnType <typeofcreateContext >>;// -------------------------------------------------// @filename: trpc.ts// -------------------------------------------------import {initTRPC ,TRPCError } from '@trpc/server';import {Context } from './context';constt =initTRPC .context <Context >().create ();export constrouter =t .router ;/*** Unprotected procedure*/export constpublicProcedure =t .procedure ;/*** Protected procedure*/export constprotectedProcedure =t .procedure .use (functionisAuthed (opts ) {if (!opts .ctx .session ?.user ?.throw newTRPCError ({code : 'UNAUTHORIZED',});}returnopts .next ({ctx : {// Infers the `session` as non-nullablesession :opts .ctx .session ,},});});
tsx
// -------------------------------------------------// @filename: context.ts// -------------------------------------------------import type {CreateNextContextOptions } from '@trpc/server/adapters/next';import {getSession } from 'next-auth/react';/*** Creates context for an incoming request* @link https://trpc.dev.org.tw/docs/v11/context*/export async functioncreateContext (opts :CreateNextContextOptions ) {constsession = awaitgetSession ({req :opts .req });return {session ,};}export typeContext =Awaited <ReturnType <typeofcreateContext >>;// -------------------------------------------------// @filename: trpc.ts// -------------------------------------------------import {initTRPC ,TRPCError } from '@trpc/server';import {Context } from './context';constt =initTRPC .context <Context >().create ();export constrouter =t .router ;/*** Unprotected procedure*/export constpublicProcedure =t .procedure ;/*** Protected procedure*/export constprotectedProcedure =t .procedure .use (functionisAuthed (opts ) {if (!opts .ctx .session ?.user ?.throw newTRPCError ({code : 'UNAUTHORIZED',});}returnopts .next ({ctx : {// Infers the `session` as non-nullablesession :opts .ctx .session ,},});});
內部和外部內容
在某些情況下,將內容拆分為「內部」和「外部」函式可能是合理的。
內部內容是定義與要求無關的內容的地方,例如資料庫連線。您可以將此函式用於整合測試或 伺服器端輔助程式,在這些情況下您沒有要求物件。在此定義的任何內容都將始終在您的程序中可用。
外部內容是定義依賴於要求的內容的地方,例如使用者的工作階段。在此定義的任何內容僅適用於透過 HTTP 呼叫的程序。
內部和外部內容範例
ts
import type { CreateNextContextOptions } from '@trpc/server/adapters/next';import { getSessionFromCookie, type Session } from './auth';/*** Defines your inner context shape.* Add fields here that the inner context brings.*/interface CreateInnerContextOptions extends Partial<CreateNextContextOptions> {session: Session | null;}/*** Inner context. Will always be available in your procedures, in contrast to the outer context.** Also useful for:* - testing, so you don't have to mock Next.js' `req`/`res`* - tRPC's `createServerSideHelpers` where we don't have `req`/`res`** @link https://trpc.dev.org.tw/docs/v11/context#inner-and-outer-context*/export async function createContextInner(opts?: CreateInnerContextOptions) {return {prisma,session: opts.session,};}/*** Outer context. Used in the routers and will e.g. bring `req` & `res` to the context as "not `undefined`".** @link https://trpc.dev.org.tw/docs/v11/context#inner-and-outer-context*/export async function createContext(opts: CreateNextContextOptions) {const session = getSessionFromCookie(opts.req);const contextInner = await createContextInner({ session });return {...contextInner,req: opts.req,res: opts.res,};}export type Context = Awaited<ReturnType<typeof createContextInner>>;// The usage in your router is the same as the example above.
ts
import type { CreateNextContextOptions } from '@trpc/server/adapters/next';import { getSessionFromCookie, type Session } from './auth';/*** Defines your inner context shape.* Add fields here that the inner context brings.*/interface CreateInnerContextOptions extends Partial<CreateNextContextOptions> {session: Session | null;}/*** Inner context. Will always be available in your procedures, in contrast to the outer context.** Also useful for:* - testing, so you don't have to mock Next.js' `req`/`res`* - tRPC's `createServerSideHelpers` where we don't have `req`/`res`** @link https://trpc.dev.org.tw/docs/v11/context#inner-and-outer-context*/export async function createContextInner(opts?: CreateInnerContextOptions) {return {prisma,session: opts.session,};}/*** Outer context. Used in the routers and will e.g. bring `req` & `res` to the context as "not `undefined`".** @link https://trpc.dev.org.tw/docs/v11/context#inner-and-outer-context*/export async function createContext(opts: CreateNextContextOptions) {const session = getSessionFromCookie(opts.req);const contextInner = await createContextInner({ session });return {...contextInner,req: opts.req,res: opts.res,};}export type Context = Awaited<ReturnType<typeof createContextInner>>;// The usage in your router is the same as the example above.
從內部內容推論您的 Context
很重要,因為只有在那裡定義的內容才真正始終在您的程序中可用。
如果您不想在程序中一直檢查 req
或 res
是否為 undefined
,您可以建立一個小型可重複使用的程序。
ts
export const apiProcedure = publicProcedure.use((opts) => {if (!opts.ctx.req || !opts.ctx.res) {throw new Error('You are missing `req` or `res` in your call.');}return opts.next({ctx: {// We overwrite the context with the truthy `req` & `res`, which will also overwrite the types used in your procedure.req: opts.ctx.req,res: opts.ctx.res,},});});
ts
export const apiProcedure = publicProcedure.use((opts) => {if (!opts.ctx.req || !opts.ctx.res) {throw new Error('You are missing `req` or `res` in your call.');}return opts.next({ctx: {// We overwrite the context with the truthy `req` & `res`, which will also overwrite the types used in your procedure.req: opts.ctx.req,res: opts.ctx.res,},});});