Context
What every call gets as ctx, where it comes from, and how middleware adds to it.
On this page
ctx is what a call knows about where it came from: the user, a database, the platform. Its type is the root ctx you give initTrpc:
import { function initTrpc<TConfig extends RootConfig = {}>(opts?: RootOptions<TConfig["meta"] extends object ? TConfig["meta"] : {}>): TrpcRoot<TConfig["ctx"] extends object ? TConfig["ctx"] : {}, TConfig["meta"] extends object ? TConfig["meta"] : {}>initTrpc } from '@trpc12/trpc/server';
interface User {
User.id: stringid: string;
}
const const t: TrpcRoot<{
user: User | null;
}, {}, RootDef<{
user: User | null;
}, {}>>
t = initTrpc<{
ctx: {
user: User | null;
};
}>(opts?: RootOptions<{}>): TrpcRoot<{
user: User | null;
}, {}, RootDef<{
user: User | null;
}, {}>>
initTrpc<{ ctx: {
user: User | null;
}
ctx: { user: User | nulluser: User | null } }>();From the request
The handler's createContext makes it for each call, from the request (and, on runtimes like Workers, the platform argument):
import { function createFetchHandler<TRouter extends AnyRouter, TPlatform = undefined>(opts: FetchHandlerOptions<TRouter, TPlatform>): FetchHandler<TPlatform>createFetchHandler } from '@trpc12/trpc/server';
const const handler: FetchHandler<undefined>handler = createFetchHandler<{
post: {
byId: Procedure<{
type: "query";
ctx: Context;
meta: {};
input: {
id: string;
};
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}> | SchemaValidationError<ZodObject<{
id: ZodString;
}, $strip>>;
services: never;
}>;
list: Procedure<{
type: "query";
ctx: Context;
meta: {};
input: undefined;
output: Post[];
errors: never;
services: never;
}>;
create: Procedure<{
type: "mutation";
ctx: Context;
meta: {};
input: {
title: string;
};
output: Post;
errors: TrpcError<...> | SchemaValidationError<...>;
services: never;
}>;
};
}, undefined>(opts: FetchHandlerOptions<...>): FetchHandler<...>
createFetchHandler({
BaseHandlerOptions<{ post: { byId: Procedure<{ type: "query"; ctx: Context; meta: {}; input: { id: string; }; output: Post; errors: TrpcError<"NOT_FOUND", { id: string; }> | SchemaValidationError<ZodObject<{ id: ZodString; }, $strip>>; services: never; }>; list: Procedure<...>; create: Procedure<...>; }; }>.router: {
post: {
byId: Procedure<{
type: "query";
ctx: Context;
meta: {};
input: {
id: string;
};
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}> | SchemaValidationError<ZodObject<{
id: ZodString;
}, $strip>>;
services: never;
}>;
list: Procedure<{
type: "query";
ctx: Context;
meta: {};
input: undefined;
output: Post[];
errors: never;
services: never;
}>;
create: Procedure<{
type: "mutation";
ctx: Context;
meta: {};
input: {
title: string;
};
output: Post;
errors: TrpcError<...> | SchemaValidationError<...>;
services: never;
}>;
};
}
router: const appRouter: {
post: {
byId: Procedure<{
type: "query";
ctx: Context;
meta: {};
input: {
id: string;
};
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}> | SchemaValidationError<ZodObject<{
id: ZodString;
}, $strip>>;
services: never;
}>;
list: Procedure<{
type: "query";
ctx: Context;
meta: {};
input: undefined;
output: Post[];
errors: never;
services: never;
}>;
create: Procedure<{
type: "mutation";
ctx: Context;
meta: {};
input: {
title: string;
};
output: Post;
errors: TrpcError<...> | SchemaValidationError<...>;
services: never;
}>;
};
}
appRouter,
createContext: (opts: CreateContextOpts<undefined>) => MaybePromise<{
user: User | null;
}>
createContext: (opts: CreateContextOpts<undefined>opts) => ({
user: User | nulluser: opts: CreateContextOpts<undefined>opts.CreateContextOpts<undefined>.request: HttpServerRequestrequest.HttpIncomingMessage<HttpServerError>.headers: Headersheaders['x-user']
? { User.id: stringid: opts: CreateContextOpts<undefined>opts.CreateContextOpts<undefined>.request: HttpServerRequestrequest.HttpIncomingMessage<HttpServerError>.headers: Headersheaders['x-user'], User.name: stringname: 'Ada' }
: null,
}),
});request is Effect's HttpServerRequest: headers is a record of lowercase names, and on fetch runtimes request.source is the web Request. On Node the request is never converted to a web Request (Node). The platform argument is described in Fetch runtimes.
WebSocket calls get their upgrade request.
From middleware
Middleware adds to the ctx with next({ ctx }), and the procedures after it see the narrowed type (Middleware). A middleware published by a package states the ctx it needs; on a procedure that lacks a key, the key becomes a requirement that createContext must supply.
In-process calls
In-process calls have no HTTP request: you pass the ctx (or a function that makes it) yourself, and the handler's createContext doesn't run. To share request-reading code with the handler, keep it in a function of the request and build one: ctx: () => contextFor(HttpServerRequest.fromWeb(new Request(url, { headers }))). See Server-side calls.
Services instead of ctx
Things built once for the whole server (a connection pool, a mailer) can be Effect services in the handler's layer instead of ctx keys: the layer is built once, and resolvers that return an Effect ask for them (Effect).