コンテキスト
このページは PageTurner AI で翻訳されました(ベータ版)。プロジェクト公式の承認はありません。 エラーを見つけましたか? 問題を報告 →
コンテキストはすべてのtRPCプロシージャがアクセスできるデータを保持し、データベース接続や認証情報などを配置するのに最適な場所です。
コンテキストの設定は2つのステップで行われます:初期化時に型を定義し、その後各リクエストごとにランタイムコンテキストを作成します。
コンテキスト型の定義
tRPCをinitTRPCで初期化する際、.create()を呼び出す前に.context<TContext>()をinitTRPCビルダー関数にパイプします。型TContextは関数の戻り値型から推論されるか、明示的に定義できます。
これにより、プロシージャとミドルウェアでコンテキストが適切に型付けされます。
tsimport {initTRPC } from '@trpc/server';import type {CreateNextContextOptions } from '@trpc/server/adapters/next';import {getSession } from 'next-auth/react';export constcreateContext = async (opts :CreateNextContextOptions ) => {constsession = awaitgetSession ({req :opts .req });return {session ,};};constt1 =initTRPC .context <typeofcreateContext >().create ();t1 .procedure .use (({ctx }) => { ... });typeContext =Awaited <ReturnType <typeofcreateContext >>;constt2 =initTRPC .context <Context >().create ();t2 .procedure .use (({ctx }) => { ... });
tsimport {initTRPC } from '@trpc/server';import type {CreateNextContextOptions } from '@trpc/server/adapters/next';import {getSession } from 'next-auth/react';export constcreateContext = async (opts :CreateNextContextOptions ) => {constsession = awaitgetSession ({req :opts .req });return {session ,};};constt1 =initTRPC .context <typeofcreateContext >().create ();t1 .procedure .use (({ctx }) => { ... });typeContext =Awaited <ReturnType <typeofcreateContext >>;constt2 =initTRPC .context <Context >().create ();t2 .procedure .use (({ctx }) => { ... });
コンテキストの作成
createContext()関数は、アプリルーターをマウントするハンドラー(HTTP経由、サーバーサイド呼び出し、またはサーバーサイドヘルパー)に渡す必要があります。
createContext()はtRPCの呼び出しごとに実行されるため、バッチ処理されたリクエストは同じコンテキストを共有します。
ts// 1. HTTP requestimport { createHTTPHandler } from '@trpc/server/adapters/standalone';import { createContext } from './context';import { createCaller } from './router';const handler = createHTTPHandler({router: appRouter,createContext,});
ts// 1. HTTP requestimport { createHTTPHandler } from '@trpc/server/adapters/standalone';import { createContext } from './context';import { createCaller } from './router';const handler = createHTTPHandler({router: appRouter,createContext,});
ts// 2. Server-side callimport { createContext } from './context';import { createCaller } from './router';const caller = createCaller(await createContext());
ts// 2. Server-side callimport { createContext } from './context';import { createCaller } from './router';const caller = createCaller(await createContext());
ts// 3. servers-side helpersimport { 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 helpersimport { 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/context*/export async functioncreateContext (opts :CreateNextContextOptions ) {constsession = awaitgetSession ({req :opts .req });return {session ,};}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 .session ?.user ?.throw newTRPCError ({code : 'UNAUTHORIZED',});}returnopts .next ({ctx : {// Infers the `session` as non-nullablesession :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/context*/export async functioncreateContext (opts :CreateNextContextOptions ) {constsession = awaitgetSession ({req :opts .req });return {session ,};}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 .session ?.user ?.throw newTRPCError ({code : 'UNAUTHORIZED',});}returnopts .next ({ctx : {// Infers the `session` as non-nullablesession :opts .ctx .session ,},});});
内部コンテキストと外部コンテキスト
シナリオによっては、コンテキストを「内部」と「外部」の関数に分割することが合理的です。
内部コンテキストはリクエストに依存しないコンテキスト(例:データベース接続)を定義する場所です。統合テストやサーバーサイドヘルパーなど、リクエストオブジェクトがない場合に使用できます。ここで定義したものはプロシージャで常に利用可能です。
外部コンテキストはリクエストに依存するコンテキスト(例:ユーザーセッション)を定義する場所です。ここで定義したものはHTTP経由で呼び出されるプロシージャでのみ利用可能です。
内部・外部コンテキストの例
tsimport 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/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/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.
tsimport 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/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/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は内部コンテキストから推論することが重要です。なぜなら、実際にプロシージャで常に利用可能なのはそこで定義されたものだけだからです。
プロシージャ内で常にreqやresがundefinedかどうかをチェックしたくない場合は、次のような小さな再利用可能なプロシージャを構築できます:
tsexport 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,},});});
tsexport 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,},});});