Using Context
The GraphQL context object gives resolvers access to shared state for the current request. For example, you can use it to make the current user available throughout your schema.
First, let's define the user data and add a Context type to the builder:
import SchemaBuilder from '@pothos/core';
interface User {
id: string;
firstName: string;
username: string;
}
const builder = new SchemaBuilder<{
Context: {
currentUser: User | null;
};
}>({});Next, we can define a GraphQL object for the user and a query that reads it from context:
const UserRef = builder.objectRef<User>('User').implement({
fields: (t) => ({
id: t.exposeID('id'),
firstName: t.exposeString('firstName'),
username: t.exposeString('username'),
}),
});
builder.queryType({
fields: (t) => ({
currentUser: t.field({
type: UserRef,
nullable: true,
resolve: (_root, _args, context) => context.currentUser,
}),
}),
});The resolver's context parameter has the type we provided to the builder. Clients can query the
current user with:
query {
currentUser {
id
username
}
}When there is no signed-in user, the response is { "data": { "currentUser": null } }.
Your GraphQL server creates the context for each request. Here is an example using GraphQL Yoga,
where getUserFromAuthHeader is an application function that looks up a user and returns User | null:
import { createServer } from 'node:http';
import { initContextCache } from '@pothos/core';
import { createYoga } from 'graphql-yoga';
import { getUserFromAuthHeader } from './auth';
const yoga = createYoga({
schema: builder.toSchema(),
context: async ({ request }) => ({
...initContextCache(),
currentUser: await getUserFromAuthHeader(request.headers.get('authorization')),
}),
});
const server = createServer(yoga);
server.listen(3000);Initialize context cache
Create a new context object for each request. Plugins such as dataloader and scope-auth use the context object's identity to cache data for that request's resolvers.
initContextCache() lets those plugins share their cache when a server copies or extends the
context. Call it inside the context factory so each request gets a separate cache key. If your server
passes the same context object to every resolver, plugins can cache by its identity without this
helper.
Context when using multiple protocols
If your HTTP and WebSocket handlers provide different context properties, a discriminated union lets resolvers check which properties are available:
type Context =
| {
transport: 'http';
request: Request;
}
| {
transport: 'websocket';
connectionParams: { clientName: string };
};
const builder = new SchemaBuilder<{
Context: Context;
}>({});
builder.queryType({
fields: (t) => ({
clientName: t.string({
nullable: true,
resolve: (_root, _args, context) => {
if (context.transport === 'http') {
return context.request.headers.get('x-client-name');
}
return context.connectionParams.clientName;
},
}),
}),
});Checking transport narrows the context type, so the resolver can access the properties for that
transport. Set the discriminator and the corresponding properties when creating the context in each
handler.