본문으로 건너뛰기
버전: 11.x

컨텍스트

컨텍스트는 모든 tRPC 프로시저에서 접근할 수 있는 데이터를 담으며, 인증 정보 같은 값을 저장하기에 적합합니다.

컨텍스트 설정은 초기화 시 타입을 정의하고 각 요청에 대해 런타임 컨텍스트를 생성하는 두 단계로 이루어집니다.

컨텍스트 타입 정의

initTRPC를 사용하여 tRPC를 초기화할 때, .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() 함수는 appRouter를 마운트하는 핸들러에 전달되어야 합니다. 핸들러는 HTTP 또는 서버 사이드 호출을 사용할 수 있습니다.

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
// 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에서 대형 클라이언트 사용 시 트레이드오프

prisma와 같은 데이터베이스 클라이언트를 createContextInner에 배치하는 것은 편리하고 일반적이지만, 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를 내부 컨텍스트에서 추론하는 것이 중요합니다.

프로시저에서 매번 undefined에 대해 req 또는 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
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,
},
});
});