Context
Your context holds data that all of your tRPC procedures will have access to, and is a great place to put things like authentication information.
Setting up the context is done in 2 steps, defining the type during initialization and then creating the runtime context for each request.
Defining the context type
When initializing tRPC using initTRPC, you should pipe .context<TContext>() to the initTRPC builder function before calling .create(). The type TContext can either be inferred from a function's return type or be explicitly defined.
This will make sure your context is properly typed in your procedures and middlewares.
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 ();});
Creating the context
The createContext() function must be passed to the handler mounting your appRouter. The handler may use HTTP or a server-side call.
createContext() is called once per request, so all procedures within a single batched request share the same context.
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 (),});
Example code
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 ,},});});
Inner and outer context
In some scenarios it could make sense to split up your context into "inner" and "outer" functions.
Inner context is where you define context which doesn’t depend on the request, e.g. your database connection. You can use this function for integration testing or server-side calls, where you don’t have a request object. Whatever is defined here will always be available in your procedures.
createContextInnerPutting a database client such as prisma on createContextInner is convenient and common, but large generated clients (like Prisma) can increase type-checking overhead because they become part of your context type across procedures.
If that overhead becomes noticeable, an alternative is to keep context smaller and import the client directly at call sites where needed.
Outer context is where you define context which depends on the request, e.g. for the user's session. Whatever is defined here is only available for procedures that are called via HTTP.
Example for inner & outer context
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.
It is important to infer your Context from the inner context, as only what is defined there is really always available in your procedures.
If you don't want to check req or res for undefined in your procedures all the time, you could build a small reusable procedure for that:
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 ,},});});
Limiting Batch Size
You can use the context to limit the number of requests that can be batched together.
tsimport {TRPCError } from '@trpc/server';import type {CreateHTTPContextOptions } from '@trpc/server/adapters/standalone';constMAX_BATCH_SIZE = 10;// Create a context that checks batch sizeexport async functioncreateContext (opts :CreateHTTPContextOptions ) {if (opts .info .calls .length >MAX_BATCH_SIZE ) {throw newTRPCError ({code : 'TOO_MANY_REQUESTS',message : `Batch size limit of ${MAX_BATCH_SIZE } exceeded`,});}return {};}
tsimport {TRPCError } from '@trpc/server';import type {CreateHTTPContextOptions } from '@trpc/server/adapters/standalone';constMAX_BATCH_SIZE = 10;// Create a context that checks batch sizeexport async functioncreateContext (opts :CreateHTTPContextOptions ) {if (opts .info .calls .length >MAX_BATCH_SIZE ) {throw newTRPCError ({code : 'TOO_MANY_REQUESTS',message : `Batch size limit of ${MAX_BATCH_SIZE } exceeded`,});}return {};}
This context will throw a TOO_MANY_REQUESTS error if a client tries to batch more than 10 requests together. You can adjust the MAX_BATCH_SIZE constant to match your needs.