skip to Main Content

The Apollo Server docs state:

Resolvers should never destructively modify the contextValue argument. This ensures consistency across all resolvers and prevents unexpected errors.

I’d like some help unpacking this statement.

  1. "Never destructively modify" I guess means that you can add properties to the context, but not remove or mutate them?
  2. "Ensures consistency across all resolvers" I guess refers to situations where query resolvers are processed in parallel such that a context value could change unexpectedly mid-execution. But what about mutation resolvers which are processed in series – would it be safe to mutate the context in that case?

My issue is this:

  1. I load the authenticated user as part of the context initialisation function.
  2. I put the user object into the context to be used by resolvers.
  3. I have a setUserDetails mutation which can update the user.
  4. If I run a query with two mutations (e.g. setUserDetails followed by sendWelcomeEmail), the second mutation sees the original user details, not the updated ones unless I mutate the context.

This leads to "unexpected errors" which was the thing we were trying to avoid in the first place.

So my question is: is it OK to mutate the context in a mutation resolver? Or is there another recommended approach to avoid this issue?

2

Answers


  1. You seem to be quite aware of the issues that can originate from mutating the context value. I think the intention of the docs is to prevent people from using the context to pass around values. I think, ideally the context is created once and then never modified.

    I think in your case, your context also somewhat serves as a cache (which is not uncommon), so I think it should be fine to invalidate or update cache entries after an update.

    Nevertheless, I would consider the following:

    • Should "sendWelcomeEmail" really be a standalone mutation? Maybe your API could be designed differently. I recommend Mark-Andre’s writing.
    • I assume that you are not really "caching" values, but this is probably only the case for the current user. It can be wise, to only store a few properties in the context taht don’t ever change (e.g. in our API we have { id: 1, companyId: 2, role: 'USER' } and load everything else from the db, when we need it. Your mutation is probably called very seldomly and does not slow down significantly, if you load the email from the database in the resolver.
    Login or Signup to reply.
  2. Running two mutations (or a mutation and a query) in the same network request carries risks if one depends on the results of the other. I don’t believe the order of execution is guaranteed, and if each makes async calls then they could interleave.

    The simplest way to approach this is to define a mutation that performs both the change in the user information and sends the welcome email. This is qualitatively different than a later mutation that just changes user information long after the user has been onboarded.

    I would also argue that the front-end really just wants to onboard the user and that it’s the back end that cares about emails and other kinds of notifications so why bring this responsibility all the way back to the front-end?

    Even if the sendWelcomeEmail mutation looked at the db for user information instead of the context there is no guarantee that the user record would have been updated by the other mutation in time.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search