上下文
本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →
上下文存储了所有 tRPC 过程均可访问的数据,非常适合存放认证信息等内容。
设置上下文分为两个步骤:在初始化阶段定义类型,然后为每个请求创建运行时上下文。
定义上下文类型
使用 initTRPC 初始化 tRPC 时,应在调用 .create() 之前通过管道操作将 .context<TContext>() 连接到 initTRPC 构建器函数。类型 TContext 既可从函数返回类型推断,也可显式定义。
这将确保你的上下文在过程和中间件中具有正确的类型提示。
tsimport {initTRPC } from '@trpc/server';import type {CreateHTTPContextOptions } from '@trpc/server/adapters/standalone';export constcreateContext = async (opts :CreateHTTPContextOptions ) => {// Example: extract a session token from the request headersconsttoken =opts .req .headers ['authorization'];return {token ,};};export typeContext =Awaited <ReturnType <typeofcreateContext >>;constt =initTRPC .context <Context >().create ();t .procedure .use ((opts ) => {opts .ctx ;returnopts .next ();});
tsimport {initTRPC } from '@trpc/server';import type {CreateHTTPContextOptions } from '@trpc/server/adapters/standalone';export constcreateContext = async (opts :CreateHTTPContextOptions ) => {// Example: extract a session token from the request headersconsttoken =opts .req .headers ['authorization'];return {token ,};};export typeContext =Awaited <ReturnType <typeofcreateContext >>;constt =initTRPC .context <Context >().create ();t .procedure .use ((opts ) => {opts .ctx ;returnopts .next ();});
创建上下文
createContext() 函数必须传递给挂载应用路由(appRouter)的处理程序。处理程序可使用 HTTP 或服务端调用。
createContext() 每个请求仅调用一次,因此单个批处理请求中的所有过程共享同一上下文。
ts// 1. HTTP requestimport {createHTTPHandler } from '@trpc/server/adapters/standalone';import {createContext } from './context';import {appRouter } from './router';consthandler =createHTTPHandler ({router :appRouter ,createContext ,});
ts// 1. HTTP requestimport {createHTTPHandler } from '@trpc/server/adapters/standalone';import {createContext } from './context';import {appRouter } from './router';consthandler =createHTTPHandler ({router :appRouter ,createContext ,});
ts// 2. Server-side callimport {createContext } from './context';import {createCaller } from './router';constcaller =createCaller (awaitcreateContext ());
ts// 2. Server-side callimport {createContext } from './context';import {createCaller } from './router';constcaller =createCaller (awaitcreateContext ());
ts// 3. Server-side helpers (Next.js-specific, see /docs/client/nextjs/pages-router/server-side-helpers)import {createServerSideHelpers } from '@trpc/react-query/server';import {createContext } from './context';import {appRouter } from './router';consthelpers =createServerSideHelpers ({router :appRouter ,ctx : awaitcreateContext (),});
ts// 3. Server-side helpers (Next.js-specific, see /docs/client/nextjs/pages-router/server-side-helpers)import {createServerSideHelpers } from '@trpc/react-query/server';import {createContext } from './context';import {appRouter } from './router';consthelpers =createServerSideHelpers ({router :appRouter ,ctx : awaitcreateContext (),});
示例代码
ts// -------------------------------------------------// @filename: context.ts// -------------------------------------------------import type {CreateHTTPContextOptions } from '@trpc/server/adapters/standalone';/*** Creates context for an incoming request* @see https://trpc.io/docs/v11/context*/export async functioncreateContext (opts :CreateHTTPContextOptions ) {consttoken =opts .req .headers ['authorization'];// In a real app, you would verify the token and look up the userconstuser =token ? {return {user ,};}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 .user ?.throw newTRPCError ({code : 'UNAUTHORIZED',});}returnopts .next ({ctx : {// Infers the `user` as non-nullableuser :opts .ctx .user ,},});});
ts// -------------------------------------------------// @filename: context.ts// -------------------------------------------------import type {CreateHTTPContextOptions } from '@trpc/server/adapters/standalone';/*** Creates context for an incoming request* @see https://trpc.io/docs/v11/context*/export async functioncreateContext (opts :CreateHTTPContextOptions ) {consttoken =opts .req .headers ['authorization'];// In a real app, you would verify the token and look up the userconstuser =token ? {return {user ,};}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 .user ?.throw newTRPCError ({code : 'UNAUTHORIZED',});}returnopts .next ({ctx : {// Infers the `user` as non-nullableuser :opts .ctx .user ,},});});
内部上下文与外部上下文
在某些场景下,将上下文拆分为"内部"和"外部"函数会更合理。
内部上下文用于定义不依赖于请求的数据(例如数据库连接)。该函数适用于集成测试或服务端调用场景(当不存在请求对象时)。此处定义的任何内容将始终在您的过程(procedures)中可用。
createContextInner 中使用大型客户端的权衡将 prisma 等数据库客户端置于 createContextInner 中虽便捷常见,但大型生成客户端(如 Prisma)会增加类型检查开销,因为它们会成为贯穿所有过程的上下文类型的一部分。
若开销变得明显,替代方案是保持上下文精简,并在需要时直接于调用位置导入客户端。
外部上下文用于定义依赖请求的数据(如用户会话),此处定义的内容仅适用于通过 HTTP 调用的过程。
内部与外部上下文示例
tsimport type {CreateHTTPContextOptions } from '@trpc/server/adapters/standalone';import {getSessionFromCookie , typeSession } from './auth';import {db } from './db';/*** Defines your inner context shape.* Add fields here that the inner context brings.*/interfaceCreateInnerContextOptions {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 `req`/`res`* - server-side calls where we don't have `req`/`res`** @see https://trpc.io/docs/v11/context#inner-and-outer-context*/export async functioncreateContextInner (opts ?:CreateInnerContextOptions ) {return {db ,session :opts ?.session ,};}/*** Outer context. Used in the routers and will e.g. bring `req` & `res` to the context as "not `undefined`".** @see https://trpc.io/docs/v11/context#inner-and-outer-context*/export async functioncreateContext (opts :CreateHTTPContextOptions ) {constsession =getSessionFromCookie (opts .req );constcontextInner = awaitcreateContextInner ({session });return {...contextInner ,req :opts .req ,res :opts .res ,};}export typeContext =Awaited <ReturnType <typeofcreateContextInner >>;// The usage in your router is the same as the example above.
tsimport type {CreateHTTPContextOptions } from '@trpc/server/adapters/standalone';import {getSessionFromCookie , typeSession } from './auth';import {db } from './db';/*** Defines your inner context shape.* Add fields here that the inner context brings.*/interfaceCreateInnerContextOptions {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 `req`/`res`* - server-side calls where we don't have `req`/`res`** @see https://trpc.io/docs/v11/context#inner-and-outer-context*/export async functioncreateContextInner (opts ?:CreateInnerContextOptions ) {return {db ,session :opts ?.session ,};}/*** Outer context. Used in the routers and will e.g. bring `req` & `res` to the context as "not `undefined`".** @see https://trpc.io/docs/v11/context#inner-and-outer-context*/export async functioncreateContext (opts :CreateHTTPContextOptions ) {constsession =getSessionFromCookie (opts .req );constcontextInner = awaitcreateContextInner ({session });return {...contextInner ,req :opts .req ,res :opts .res ,};}export typeContext =Awaited <ReturnType <typeofcreateContextInner >>;// The usage in your router is the same as the example above.
必须从内部上下文推断你的 Context 类型,因为只有此处定义的内容才是始终可用的。
如果不想在过程中反复检查 req 或 res 是否为 undefined,可以创建小型可复用过程来实现:
tsexport constapiProcedure =publicProcedure .use ((opts ) => {if (!opts .ctx .req || !opts .ctx .res ) {throw newError ('You are missing `req` or `res` in your call.');}returnopts .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 ,},});});
tsexport constapiProcedure =publicProcedure .use ((opts ) => {if (!opts .ctx .req || !opts .ctx .res ) {throw newError ('You are missing `req` or `res` in your call.');}returnopts .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 ,},});});