跳至主要內容
版本:11.x

脈絡

您的脈絡持有所有 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 const createContext = async (opts: CreateNextContextOptions) => {
const session = await getSession({ req: opts.req });
 
return {
session,
};
};
 
const t1 = initTRPC.context<typeof createContext>().create();
t1.procedure.use(({ ctx }) => { ... });
(parameter) ctx: { session: Session | null; }
 
type Context = Awaited<ReturnType<typeof createContext>>;
const t2 = initTRPC.context<Context>().create();
t2.procedure.use(({ ctx }) => { ... });
(parameter) ctx: { session: Session | null; }
ts
import { initTRPC } from '@trpc/server';
import type { CreateNextContextOptions } from '@trpc/server/adapters/next';
import { getSession } from 'next-auth/react';
 
export const createContext = async (opts: CreateNextContextOptions) => {
const session = await getSession({ req: opts.req });
 
return {
session,
};
};
 
const t1 = initTRPC.context<typeof createContext>().create();
t1.procedure.use(({ ctx }) => { ... });
(parameter) ctx: { session: Session | null; }
 
type Context = Awaited<ReturnType<typeof createContext>>;
const t2 = initTRPC.context<Context>().create();
t2.procedure.use(({ ctx }) => { ... });
(parameter) ctx: { session: Session | null; }

建立脈絡

createContext() 函數必須傳遞至掛載 appRouter 的處理常式,這可能是透過 HTTP、伺服器端呼叫 或我們的 伺服器端輔助程式

每次呼叫 tRPC 時,都會呼叫 createContext(),因此批次要求會共用一個內容。

ts
// 1. HTTP request
import { createHTTPHandler } from '@trpc/server/adapters/standalone';
import { createContext } from './context';
import { createCaller } from './router';
const handler = createHTTPHandler({
router: appRouter,
createContext,
});
ts
// 1. HTTP request
import { createHTTPHandler } from '@trpc/server/adapters/standalone';
import { createContext } from './context';
import { createCaller } from './router';
const handler = createHTTPHandler({
router: appRouter,
createContext,
});
ts
// 2. Server-side call
import { createContext } from './context';
import { createCaller } from './router';
const caller = createCaller(await createContext());
ts
// 2. Server-side call
import { createContext } from './context';
import { createCaller } from './router';
const caller = createCaller(await createContext());
ts
// 3. servers-side helpers
import { 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 helpers
import { 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 function createContext(opts: CreateNextContextOptions) {
const session = await getSession({ req: opts.req });
 
return {
session,
};
}
 
export type Context = Awaited<ReturnType<typeof createContext>>;
 
// -------------------------------------------------
// @filename: trpc.ts
// -------------------------------------------------
import { initTRPC, TRPCError } from '@trpc/server';
import { Context } from './context';
 
const t = initTRPC.context<Context>().create();
 
 
export const router = t.router;
 
/**
* Unprotected procedure
*/
export const publicProcedure = t.procedure;
 
/**
* Protected procedure
*/
export const protectedProcedure = t.procedure.use(function isAuthed(opts) {
if (!opts.ctx.session?.user?.email) {
throw new TRPCError({
code: 'UNAUTHORIZED',
});
}
return opts.next({
ctx: {
// Infers the `session` as non-nullable
session: 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 function createContext(opts: CreateNextContextOptions) {
const session = await getSession({ req: opts.req });
 
return {
session,
};
}
 
export type Context = Awaited<ReturnType<typeof createContext>>;
 
// -------------------------------------------------
// @filename: trpc.ts
// -------------------------------------------------
import { initTRPC, TRPCError } from '@trpc/server';
import { Context } from './context';
 
const t = initTRPC.context<Context>().create();
 
 
export const router = t.router;
 
/**
* Unprotected procedure
*/
export const publicProcedure = t.procedure;
 
/**
* Protected procedure
*/
export const protectedProcedure = t.procedure.use(function isAuthed(opts) {
if (!opts.ctx.session?.user?.email) {
throw new TRPCError({
code: 'UNAUTHORIZED',
});
}
return opts.next({
ctx: {
// Infers the `session` as non-nullable
session: 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 很重要,因為只有在那裡定義的內容才真正始終在您的程序中可用。

如果您不想在程序中一直檢查 reqres 是否為 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,
},
});
});