컨텍스트
컨텍스트는 모든 tRPC 프로시저에서 접근할 수 있는 데이터를 담으며, 인증 정보 같은 값을 저장하기에 적합합니다.
컨텍스트 설정은 초기화 시 타입을 정의하고 각 요청에 대해 런타임 컨텍스트를 생성하는 두 단계로 이루어집니다.
컨텍스트 타입 정의
initTRPC를 사용하여 tRPC를 초기화할 때, .create()를 호출하기 전에 .context<TContext>()를 initTRPC 빌더 함수에 연결해야 합니다. TContext 타입은 함수의 반환 타입에서 추론되거나 명시적으로 정의될 수 있습니다.
이렇게 하면 프로시저와 미들웨어에서 컨텍스트가 올바르게 타입 지정됩니다.
tsimport {initTRPC } from '@trpc/server';import type {CreateHTTPContextOptions } from '@trpc/server/adapters/standalone';export constcreateContext = async (opts :CreateHTTPContextOptions ) => {// Example: extract a session token from the request headersconsttoken =opts .req .headers ['authorization'];return {token ,};};export typeContext =Awaited <ReturnType <typeofcreateContext >>;constt =initTRPC .context <Context >().create ();t .procedure .use ((opts ) => {opts .ctx ;returnopts .next ();});
tsimport {initTRPC } from '@trpc/server';import type {CreateHTTPContextOptions } from '@trpc/server/adapters/standalone';export constcreateContext = async (opts :CreateHTTPContextOptions ) => {// Example: extract a session token from the request headersconsttoken =opts .req .headers ['authorization'];return {token ,};};export typeContext =Awaited <ReturnType <typeofcreateContext >>;constt =initTRPC .context <Context >().create ();t .procedure .use ((opts ) => {opts .ctx ;returnopts .next ();});
컨텍스트 생성
createContext() 함수는 appRouter를 마운트하는 핸들러에 전달되어야 합니다. 핸들러는 HTTP 또는 서버 사이드 호출을 사용할 수 있습니다.
createContext()는 요청당 한 번만 호출되므로, 단일 배치 요청 내의 모든 프로시저는 동일한 컨텍스트를 공유합니다.
ts// 1. HTTP requestimport {createHTTPHandler } from '@trpc/server/adapters/standalone';import {createContext } from './context';import {appRouter } from './router';consthandler =createHTTPHandler ({router :appRouter ,createContext ,});
ts// 1. HTTP requestimport {createHTTPHandler } from '@trpc/server/adapters/standalone';import {createContext } from './context';import {appRouter } from './router';consthandler =createHTTPHandler ({router :appRouter ,createContext ,});
ts// 2. Server-side callimport {createContext } from './context';import {createCaller } from './router';constcaller =createCaller (awaitcreateContext ());
ts// 2. Server-side callimport {createContext } from './context';import {createCaller } from './router';constcaller =createCaller (awaitcreateContext ());
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';consthelpers =createServerSideHelpers ({router :appRouter ,ctx : awaitcreateContext (),});
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';consthelpers =createServerSideHelpers ({router :appRouter ,ctx : awaitcreateContext (),});
예제 코드
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 functioncreateContext (opts :CreateHTTPContextOptions ) {consttoken =opts .req .headers ['authorization'];// In a real app, you would verify the token and look up the userconstuser =token ? {return {user ,};}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 .user ?.throw newTRPCError ({code : 'UNAUTHORIZED',});}returnopts .next ({ctx : {// Infers the `user` as non-nullableuser :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 functioncreateContext (opts :CreateHTTPContextOptions ) {consttoken =opts .req .headers ['authorization'];// In a real app, you would verify the token and look up the userconstuser =token ? {return {user ,};}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 .user ?.throw newTRPCError ({code : 'UNAUTHORIZED',});}returnopts .next ({ctx : {// Infers the `user` as non-nullableuser :opts .ctx .user ,},});});
내부 및 외부 컨텍스트
일부 시나리오에서는 컨텍스트를 "내부"와 "외부" 함수로 나누는 것이 합리적일 수 있습니다.
내부 컨텍스트는 요청에 의존하지 않는 컨텍스트를 정의하는 곳으로, 예를 들어 데이터베이스 연결을 포함합니다. 이 함수는 요청 객체가 없는 통합 테스트나 서버 사이드 호출에 사용할 수 있습니다. 여기에 정의된 항목은 프로시저에서 항상 사용할 수 있습니다.
createContextInner에서 대형 클라이언트 사용 시 트레이드오프prisma와 같은 데이터베이스 클라이언트를 createContextInner에 배치하는 것은 편리하고 일반적이지만, Prisma와 같이 생성된 대형 클라이언트는 프로시저 전반에 걸쳐 컨텍스트 타입의 일부가 되어 타입 검사 오버헤드를 증가시킬 수 있습니다.
이 오버헤드가 눈에 띄게 되면, 컨텍스트를 작게 유지하고 필요한 호출 지점에서 클라이언트를 직접 가져오는 것이 대안이 될 수 있습니다.
외부 컨텍스트는 요청에 의존하는 컨텍스트를 정의하는 곳으로, 예를 들어 사용자 세션을 포함합니다. 여기에 정의된 항목은 HTTP를 통해 호출되는 프로시저에서만 사용할 수 있습니다.
내부 및 외부 컨텍스트 예제
tsimport type {CreateHTTPContextOptions } from '@trpc/server/adapters/standalone';import {getSessionFromCookie , typeSession } from './auth';import {db } from './db';/*** Defines your inner context shape.* Add fields here that the inner context brings.*/interfaceCreateInnerContextOptions {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 functioncreateContextInner (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 functioncreateContext (opts :CreateHTTPContextOptions ) {constsession =getSessionFromCookie (opts .req );constcontextInner = awaitcreateContextInner ({session });return {...contextInner ,req :opts .req ,res :opts .res ,};}export typeContext =Awaited <ReturnType <typeofcreateContextInner >>;// The usage in your router is the same as the example above.
tsimport type {CreateHTTPContextOptions } from '@trpc/server/adapters/standalone';import {getSessionFromCookie , typeSession } from './auth';import {db } from './db';/*** Defines your inner context shape.* Add fields here that the inner context brings.*/interfaceCreateInnerContextOptions {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 functioncreateContextInner (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 functioncreateContext (opts :CreateHTTPContextOptions ) {constsession =getSessionFromCookie (opts .req );constcontextInner = awaitcreateContextInner ({session });return {...contextInner ,req :opts .req ,res :opts .res ,};}export typeContext =Awaited <ReturnType <typeofcreateContextInner >>;// The usage in your router is the same as the example above.
프로시저에서 실제로 항상 사용할 수 있는 것은 내부 컨텍스트에 정의된 항목뿐이므로, Context를 내부 컨텍스트에서 추론하는 것이 중요합니다.
프로시저에서 매번 undefined에 대해 req 또는 res를 확인하고 싶지 않다면, 이를 위한 작은 재사용 가능한 프로시저를 구축할 수 있습니다:
tsexport constapiProcedure =publicProcedure .use ((opts ) => {if (!opts .ctx .req || !opts .ctx .res ) {throw newError ('You are missing `req` or `res` in your call.');}returnopts .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 constapiProcedure =publicProcedure .use ((opts ) => {if (!opts .ctx .req || !opts .ctx .res ) {throw newError ('You are missing `req` or `res` in your call.');}returnopts .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 ,},});});