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

コンテキスト

非公式ベータ版翻訳

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

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

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

コンテキスト型の定義

tRPCをinitTRPCで初期化する際、.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,
};
};
 
export type Context = Awaited<ReturnType<typeof createContext>>;
const t = initTRPC.context<Context>().create();
 
t.procedure.use((opts) => {
opts.ctx;
(property) ctx: { session: Session | null; }
 
return opts.next();
});
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,
};
};
 
export type Context = Awaited<ReturnType<typeof createContext>>;
const t = initTRPC.context<Context>().create();
 
t.procedure.use((opts) => {
opts.ctx;
(property) ctx: { session: Session | null; }
 
return opts.next();
});

コンテキストの作成

createContext()関数は、アプリルーターをマウントするハンドラー(HTTP経由、サーバーサイド呼び出し、またはサーバーサイドヘルパー)に渡す必要があります。

createContext()はtRPCの呼び出しごとに実行されるため、バッチ処理されたリクエストは同じコンテキストを共有します。

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. 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
* @see https://trpc.io/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
* @see https://trpc.io/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`
*
* @see https://trpc.io/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`".
*
* @see https://trpc.io/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`
*
* @see https://trpc.io/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`".
*
* @see https://trpc.io/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内部コンテキストから推論することが重要です。なぜなら、実際にプロシージャで常に利用可能なのはそこで定義されたものだけだからです。

プロシージャ内で常に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,
},
});
});

バッチサイズの制限

コンテキストを使用して、まとめてバッチ処理できるリクエストの数を制限できます。

ts
import { TRPCError } from '@trpc/server';
import type { CreateHTTPContextOptions } from '@trpc/server/adapters/standalone';
 
const MAX_BATCH_SIZE = 10;
 
// Create a context that checks batch size
export async function createContext(opts: CreateHTTPContextOptions) {
if (opts.info.calls.length > MAX_BATCH_SIZE) {
throw new TRPCError({
code: 'TOO_MANY_REQUESTS',
message: `Batch size limit of ${MAX_BATCH_SIZE} exceeded`,
});
}
return {};
}
ts
import { TRPCError } from '@trpc/server';
import type { CreateHTTPContextOptions } from '@trpc/server/adapters/standalone';
 
const MAX_BATCH_SIZE = 10;
 
// Create a context that checks batch size
export async function createContext(opts: CreateHTTPContextOptions) {
if (opts.info.calls.length > MAX_BATCH_SIZE) {
throw new TRPCError({
code: 'TOO_MANY_REQUESTS',
message: `Batch size limit of ${MAX_BATCH_SIZE} exceeded`,
});
}
return {};
}

このコンテキストは、クライアントが10個以上のリクエストをバッチ処理しようとするとTOO_MANY_REQUESTSエラーをスローします。MAX_BATCH_SIZE定数は必要に応じて調整できます。