Pothos v4 is now available! 🎉Check out the full migration guide here

Pothos

Using Context

The GraphQL context object can be used to give every resolver in the schema access to some shared state for the current request. One common use case is to store the current User on the context object.

One important thing to note about Pothos is that every request is assumed to have a new unique context object, so be sure to set up your context objects in a way that they are unique to each request.

First let's define a User class that holds information about a user, and create a SchemaBuilder with a Context type that has a currentUser property.

class User {
  id: string;
  firstName: string;
  username: string;
 
  constructor(id: string, firstName: string, username: string) {
    this.id = id;
    this.firstName = firstName;
    this.username = username;
  }
}
 
const builder = new SchemaBuilder<{
  Context: {
    currentUser: User;
  };
}>({});

Next, we will want to add something in our schema that uses the current user:

builder.queryType({
  fields: (t) => ({
    currentUser: t.field({
      type: User,
      resolve: (root, args, context) => context.currentUser,
    }),
  }),
});
 
builder.objectType(User, {
  fields: (t) => ({
    id: t.exposeID('id', {}),
    firstName: t.exposeString('firstName', {}),
    username: t.exposeString('username', {}),
  }),
});

Finally, we need to actually create our context when a request is created.

const yoga = createYoga({
  schema,
  context: async ({ req }) => ({
    // This part is up to you!
    currentUser: await getUserFromAuthHeader(req.headers.authorization),
  }),
});
const server = createServer(yoga);
 
server.listen(3000);

Initialize context cache

Several Pothos plugins use the context object to cache data for the current request. Some examples include dataloaders and auth scopes. This caching mechanism works based on the assumption that the same context object is passed to every resolver in a request, and each request has a unique context object. This works for most applications without any additional configuration.

In some rare edge cases, you may have some additional logic added to your application that clones or mutates the context object throughout the execution of a request. To ensure that all plugins work correctly even if the context object is cloned, wrapped, or modified in a way that does not preserve its identity, you can manually initialize the context cache and attach it to the context object:

import { initContextCache } from '@pothos/core';
 
const server = createYoga({
  schema: builder.toSchema(),
  context: async ({ req }) => ({
    // Adding this will prevent any issues if you server implementation
    // copies or extends the context object before passing it to your resolvers
    ...initContextCache(),
 
    currentUser: await getUserFromAuthHeader(req.headers.authorization),
  }),
});
 
const server = createServer(yoga);
 
server.listen(3000);

Context when using multiple protocols

In some specific situations multiple protocols could be used for handling the graphql operations against the same executable graphql schema. One common example of this is using HTTP(Hypertext Transfer Protocol) protocol for handling graphql query and mutation operations and using Websocket protocol for handling graphql subscription operations. Because the protocols are different, the protocol specific information that might be passed in the graphql context could differ depending on the graphql operation that is being executed. Now, our personal recommendation is to keep your executable graphql schema and its inner layers protocol agnostic to not have to deal with a situation like this.

We're working with two different graphql contexts within our graphql resolvers and we want strong type-safety while working with them. For this use case we recommend using typescript discriminated unions for combining types for different graphql contexts into a single union type that can be passed to pothos schema builder initializer. In the following example Context is a union type between graphql context types for HTTP and Websocket protocol specific graphql contexts, where the isSubscription boolean field is the discriminator. This context type is passed as the type for Context field in the generic accepted by the pothos schema builder initializer. Within the resolver implementations for a graphql schema created using this pothos schema builder, the graphql context can be discriminated between its two protocol specific types by using the isSubscription field. This would help us get the type-safe graphql context that we can make use of in our graphql resolvers. In the following code we perform this discrimination by checking the value of isSubscription boolean field in the if and else blocks within the graphql resolvers:

type Context =
  | {
      isSubscription: false;
      http: "HTTP specific context field."
    }
  | {
      isSubscription: true;
      websocket: "Websocket specific context field.";
    };
 
const builder = new SchemaBuilder<{
  Context: Context;
}>({});
 
builder.mutationType({
  fields: (t) => ({
    incrementCount: t.int({
      resolve: (parent, args, ctx) => {
        if (ctx.isSubscription === false) {
          // Access the HTTP protocol specific context fields.
          ctx.http;
        } else {
          // Access the Websocket protocol specific context fields.
          ctx.websocket;
        }
      },
    }),
  }),
});
 
builder.subscriptionType({
  fields: (t) => ({
    currentCount: t.int({
      subscribe: (parent, args, ctx) => {
        if (ctx.isSubscription === false) {
          // Access the HTTP protocol specific context fields.
          ctx.http;
        } else {
          // Access the Websocket protocol specific context fields.
          ctx.websocket;
        }
      },
    }),
  }),
});

On this page