Skip to main content
Version: 10.x

上下文

你的上下文保存所有 tRPC 过程都可以访问的数据,并且是放置数据库连接或身份验证信息等内容的好地方。

¥Your context holds data that all of your tRPC procedures will have access to, and is a great place to put things like database connections or authentication information.

设置上下文分两步完成,在初始化期间定义类型,然后为每个请求创建运行时上下文。

¥Setting up the context is done in 2 steps, defining the type during initialization and then creating the runtime context for each request.

定义上下文类型

¥Defining the context type

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

¥When initializing tRPC using initTRPC, you should pipe .context<TContext>() to the initTRPC builder function before calling .create(). The type TContext can either be inferred from a function's return type or be explicitly defined.

这将确保你的上下文在你的过程和中间件中正确输入。

¥This will make sure your context is properly typed in your procedures and middlewares.

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: any
 
type Context = Awaited<ReturnType<typeof createContext>>;
const t2 = initTRPC.context<Context>().create();
t2.procedure.use(({ ctx }) => { ... });
(parameter) ctx: any
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: any
 
type Context = Awaited<ReturnType<typeof createContext>>;
const t2 = initTRPC.context<Context>().create();
t2.procedure.use(({ ctx }) => { ... });
(parameter) ctx: any

创建上下文

¥Creating the context

createContext() 函数必须传递给正在安装 appRouter 的处理程序,这可能是通过 HTTP、服务器端调用 或我们的 服务器端助手

¥The createContext() function must be passed to the handler that is mounting your appRouter, which may be via HTTP, a server-side call or our server-side helpers.

每次调用 tRPC 都会调用 createContext(),因此批量请求将共享上下文。

¥createContext() is called for each invocation of tRPC, so batched requests will share a context.

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(),
});

示例代码

¥Example code

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.nodejs.cn/docs/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.nodejs.cn/docs/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,
},
});
});

内部和外部环境

¥Inner and outer context

在某些情况下,将上下文分为 "inner" 和 "outer" 函数可能是有意义的。

¥In some scenarios it could make sense to split up your context into "inner" and "outer" functions.

内部上下文是你定义不依赖于请求的上下文的地方,例如 你的数据库连接。你可以使用此函数进行集成测试或 服务器端助手,其中你没有请求对象。此处定义的任何内容都将始终在你的过程中可用。

¥Inner context is where you define context which doesn’t depend on the request, e.g. your database connection. You can use this function for integration testing or server-side helpers, where you don’t have a request object. Whatever is defined here will always be available in your procedures.

外部上下文是你定义取决于请求的上下文的地方,例如 对于用户的会话。此处定义的内容仅适用于通过 HTTP 调用的过程。

¥Outer context is where you define context which depends on the request, e.g. for the user's session. Whatever is defined here is only available for procedures that are called via HTTP.

内部和外部上下文的示例

¥Example for inner & outer context

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`
* * @see https://trpc.nodejs.cn/docs/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`".
* * @see https://trpc.nodejs.cn/docs/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`
* * @see https://trpc.nodejs.cn/docs/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`".
* * @see https://trpc.nodejs.cn/docs/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 非常重要,因为只有那里定义的内容才真正在你的过程中可用。

¥It is important to infer your Context from the inner context, as only what is defined there is really always available in your procedures.

如果你不想一直在程序中检查 reqres 中的 undefined,你可以为此构建一个小型可重用程序:

¥If you don't want to check req or res for undefined in your procedures all the time, you could build a small reusable procedure for that:

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,
},
});
});