跳至主内容
版本:11.x

上下文

非官方测试版翻译

本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →

上下文存储了所有 tRPC 过程均可访问的数据,非常适合存放认证信息等内容。

设置上下文分为两个步骤:在初始化阶段定义类型,然后为每个请求创建运行时上下文。

定义上下文类型

使用 initTRPC 初始化 tRPC 时,应在调用 .create() 之前通过管道操作将 .context<TContext>() 连接到 initTRPC 构建器函数。类型 TContext 既可从函数返回类型推断,也可显式定义。

这将确保你的上下文在过程和中间件中具有正确的类型提示。

ts
import { initTRPC } from '@trpc/server';
import type { CreateHTTPContextOptions } from '@trpc/server/adapters/standalone';
 
export const createContext = async (opts: CreateHTTPContextOptions) => {
// Example: extract a session token from the request headers
const token = opts.req.headers['authorization'];
 
return {
token,
};
};
 
export type Context = Awaited<ReturnType<typeof createContext>>;
const t = initTRPC.context<Context>().create();
 
t.procedure.use((opts) => {
opts.ctx;
(property) ctx: { token: any; }
 
return opts.next();
});
ts
import { initTRPC } from '@trpc/server';
import type { CreateHTTPContextOptions } from '@trpc/server/adapters/standalone';
 
export const createContext = async (opts: CreateHTTPContextOptions) => {
// Example: extract a session token from the request headers
const token = opts.req.headers['authorization'];
 
return {
token,
};
};
 
export type Context = Awaited<ReturnType<typeof createContext>>;
const t = initTRPC.context<Context>().create();
 
t.procedure.use((opts) => {
opts.ctx;
(property) ctx: { token: any; }
 
return opts.next();
});

创建上下文

createContext() 函数必须传递给挂载应用路由(appRouter)的处理程序。处理程序可使用 HTTP 或服务端调用

createContext() 每个请求仅调用一次,因此单个批处理请求中的所有过程共享同一上下文。

ts
// 1. HTTP request
import { createHTTPHandler } from '@trpc/server/adapters/standalone';
import { createContext } from './context';
import { appRouter } from './router';
 
const handler = createHTTPHandler({
router: appRouter,
createContext,
});
ts
// 1. HTTP request
import { createHTTPHandler } from '@trpc/server/adapters/standalone';
import { createContext } from './context';
import { appRouter } 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. 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';
 
const helpers = createServerSideHelpers({
router: appRouter,
ctx: await createContext(),
});
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';
 
const helpers = createServerSideHelpers({
router: appRouter,
ctx: await createContext(),
});

示例代码

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 function createContext(opts: CreateHTTPContextOptions) {
const token = opts.req.headers['authorization'];
 
// In a real app, you would verify the token and look up the user
const user = token ? { email: 'user@example.com' } : null;
 
return {
user,
};
}
 
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.user?.email) {
throw new TRPCError({
code: 'UNAUTHORIZED',
});
}
return opts.next({
ctx: {
// Infers the `user` as non-nullable
user: 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 function createContext(opts: CreateHTTPContextOptions) {
const token = opts.req.headers['authorization'];
 
// In a real app, you would verify the token and look up the user
const user = token ? { email: 'user@example.com' } : null;
 
return {
user,
};
}
 
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.user?.email) {
throw new TRPCError({
code: 'UNAUTHORIZED',
});
}
return opts.next({
ctx: {
// Infers the `user` as non-nullable
user: opts.ctx.user,
},
});
});

内部上下文与外部上下文

在某些场景下,将上下文拆分为"内部"和"外部"函数会更合理。

内部上下文用于定义不依赖于请求的数据(例如数据库连接)。该函数适用于集成测试或服务端调用场景(当不存在请求对象时)。此处定义的任何内容将始终在您的过程(procedures)中可用。

createContextInner 中使用大型客户端的权衡

prisma 等数据库客户端置于 createContextInner 中虽便捷常见,但大型生成客户端(如 Prisma)会增加类型检查开销,因为它们会成为贯穿所有过程的上下文类型的一部分。

若开销变得明显,替代方案是保持上下文精简,并在需要时直接于调用位置导入客户端。

外部上下文用于定义依赖请求的数据(如用户会话),此处定义的内容仅适用于通过 HTTP 调用的过程。

内部与外部上下文示例

ts
import type { CreateHTTPContextOptions } from '@trpc/server/adapters/standalone';
import { getSessionFromCookie, type Session } from './auth';
import { db } from './db';
 
/**
* Defines your inner context shape.
* Add fields here that the inner context brings.
*/
interface CreateInnerContextOptions {
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 function createContextInner(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 function createContext(opts: CreateHTTPContextOptions) {
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 { CreateHTTPContextOptions } from '@trpc/server/adapters/standalone';
import { getSessionFromCookie, type Session } from './auth';
import { db } from './db';
 
/**
* Defines your inner context shape.
* Add fields here that the inner context brings.
*/
interface CreateInnerContextOptions {
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 function createContextInner(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 function createContext(opts: CreateHTTPContextOptions) {
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,
},
});
});