メインコンテンツへスキップ
バージョン: 11.x

コンテキスト

非公式ベータ版翻訳

このページは PageTurner AI で翻訳されました(ベータ版)。プロジェクト公式の承認はありません。 エラーを見つけましたか? 問題を報告 →

コンテキストは、すべてのtRPCプロシージャがアクセスできるデータを保持しており、認証情報などを配置するのに最適な場所です。

コンテキストの設定は2つのステップで行われます:初期化時に型を定義し、その後各リクエストごとにランタイムコンテキストを作成します。

コンテキスト型の定義

tRPCをinitTRPCで初期化する際、.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()関数は、アプリケーションルーターをマウントするハンドラーに渡す必要があります。ハンドラーはHTTPまたはサーバーサイド呼び出しを使用する場合があります。

createContext()はリクエストごとに1回呼び出されるため、単一のバッチ処理されたリクエスト内のすべてのプロシージャが同じコンテキストを共有します。

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

内部コンテキストと外部コンテキスト

シナリオによっては、コンテキストを「内部」と「外部」の関数に分割することが合理的です。

内部コンテキストは、リクエストに依存しないコンテキスト(例:データベース接続)を定義する場所です。この関数は統合テストやサーバーサイド呼び出し(リクエストオブジェクトがない場合)で使用できます。ここで定義したものはプロシージャ内で常に利用可能になります。

createContextInnerでの大規模クライアントに関するトレードオフ

createContextInnerprismaのようなデータベースクライアントを配置することは便利で一般的ですが、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内部コンテキストから推論することが重要です。なぜなら、実際にプロシージャで常に利用可能なのはそこで定義されたものだけだからです。

プロシージャ内で常にreqresundefinedかどうかをチェックしたくない場合は、次のような小さな再利用可能なプロシージャを構築できます:

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