コンテキスト
コンテキストには、すべての tRPC プロシージャがアクセスできるデータが保持されます。データベース接続や認証情報などを配置するのに最適な場所です。
コンテキストの設定は、初期化中に型を定義し、リクエストごとにランタイムコンテキストを作成するという 2 つのステップで行います。
コンテキスト型の定義
initTRPC
を使用して tRPC を初期化する場合、.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 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 }) => { ... });
ts
import {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、サーバーサイド呼び出し、または サーバーサイドヘルパー を介して appRouter をマウントするハンドラーに渡す必要があります。
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* @link https://trpc.dokyumento.jp/docs/v11/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* @link https://trpc.dokyumento.jp/docs/v11/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 経由で呼び出されるプロシージャでのみ使用できます。
内部コンテキストと外部コンテキストの例
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`** @link https://trpc.dokyumento.jp/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`".** @link https://trpc.dokyumento.jp/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`** @link https://trpc.dokyumento.jp/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`".** @link https://trpc.dokyumento.jp/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
を推測することが重要です。なぜなら、そこで定義されたものだけがプロシージャで常に使用できるからです。
プロシージャで常に req
または res
の undefined
をチェックしたくない場合は、 dafür eine kleine wiederverwendbare Prozedur erstellen können
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,},});});