0

If you pass a modified context to a GraphQL resolver does this propagate to all downstream resolvers? Is this specified in the GraphQL specification or implementation specific?

To clarify with an example say I have a query like the following

{
  companies {
    employees {
      positions {
        title
      }
    }
  }
}

let's say I start with contextA coming into the companies query and then I have my CompanyResolvers where I get a superSpecialContext and pass this on to the employees dataloader

export const CompanyResolvers = {
    employees: async ({ id }: CompanyDTO, args: object, contextA: Context) => {
      const superSpecialContext = await getSuperSpecialContext();
      return context.dataLoaders.employees.load({ id: company.id, context: superSpecialContext });
    }
};

when I get to the positions resolver am I now working with the superSpecialContext or the original contextA (I would actually prefer this to be the case)?

export const EmployeeResolvers = {
    positions: async ({ id }: EmployeeDTO, args: object, context: Context) => {
      // is my context here contextA or superSpecialContext?
    }
};
Jordan
  • 5,085
  • 7
  • 34
  • 50

1 Answers1

2

If you pass a modified context to a GraphQL resolver does this propagate to all downstream resolvers.

Yes, each request gets its own context object for the duration of the request. It gets created in the context function on the GraphQL server.

import { ApolloServer, gql } from 'apollo-server'
import { ExpressContext } from 'apollo-server-express/dist/ApolloServer';

const typeDefs = gql`
  type Book {
    title: String
    author: String
  }

  type Query {
    books: [Book]
  }
`;

const books = [
  {
    title: 'Harry Potter and the Chamber of Secrets',
    author: 'J.K. Rowling',
  },
  {
    title: 'Jurassic Park',
    author: 'Michael Crichton',
  },
];

const resolvers = {
  Query: {
    books: (obj: any, args: any, context: any) => {
      console.log(context.name);  // Khalil Stemmler
      context.name = 'Billy Bob Thorton'
      return books;
    },
  },
  Book: {
    title: (obj: any, args: any, context: any) => {
      console.log(context.name); // Billy Bob Thorton.
      // Should print "Billy Bob Thorton twice", once for each book.
      return obj.title
    },
  }
};

const server = new ApolloServer({ 
  typeDefs, 
  resolvers,
  context: (expressContext: ExpressContext) => {
    // The Apollo implementation of context allows you hook into the
    // Express request to get access to headers, tokens, etc- in order 
    // to grab an authenticated user's session data and put it on context.
    const { connection, res, req } = expressContext;

    // A new context object is created for every request. This function
    // should return an object.
    return {
      name: 'Khalil Stemmler'
    }
  }
});

// The `listen` method launches a web server.
server.listen().then(({ url }: { url: string }) => {
  console.log(`  Server ready at ${url}`);
});

Running the following query:

{
  books {
    title
    author
  }
}

We get:

  Server ready at http://localhost:4000/
Khalil Stemmler
Billy Bob Thorton
Billy Bob Thorton

Reference: "Context Argument - Apollo Docs".

stemmlerjs
  • 23
  • 1
  • 6
  • actually after thinking about this further it should have been obvious although I'd like to double check DataLoader doesn't do any weird magic - for basic resolvers obviously you're not going to get the new context for downstream resolvers because this happens outside of graphql (in the example it's not like you're passing a context back from `books` that's then going on to `title`). But yeah to your point if you modify the context object itself then of course this will change downstream because it's still the same object. – Jordan Dec 12 '19 at 04:48