2
votes

If I have 2 types: User and Note with the following schema:

query {
   getUser(userId: ID!): User
}

type User {
   userId: ID
   email: String
   notes: [Note]
}

type Note {
   noteId: ID
   text: String
}

I am writing a resolver for User#notes. Now say notes need to be retrieved by email address, so I actually need the root object passed to the resolver to contain the email field, is there anyway I can force GraphQL to query the email field in the User object even if the user has not requested it?

In terms of code, from what I see, this is how I can write a resolver. How can I ensure obj.email is requested whenever the user requests the note field?

User:  {
  notes(obj, args, context, info) {
    // How can I ensure obj.email is requested?
    return NoteRetriever.getNotesByEmail(obj.email);
  }
}

Edit

I am wondering about the case where the parent resolver doesn't resolve the email field unless explicitly requested. What if we need to make an API call to get the email for the user? So by default we don't request it. However, when the notes is requested, it makes sense to request the email too.

Is there a way for the resolver to specify dependency on parent fields - to ensure that gets requested?

2

2 Answers

3
votes

The "parent" value passed to your resolver as the first parameter is exactly what was returned in the parent field's resolver (unless a Promise was returned, in which case it will be whatever the Promise resolved to). So if we have a resolver like this:

Query: {
  getUser: () => {
    return {
      userId: 10,
      email: '[email protected]',
      foobar: 42,
    }
  }
}

and a query like:

query {
  getUser {
    id
    notes
  }
}

What's passed to our notes resolver is the entire object we returned inside the resolver for getUser.

User:  {
  notes(obj, args, context, info) {
    console.log(obj.userId) // 10
    console.log(obj.email)  // "[email protected]"
    console.log(obj.foobar) // 42
  }
}

The parent value will be the same, regardless of the fields requested, unless the parent field resolver's logic actually returns a different value depending on the requested fields. This means you can also pass down any number of other, arbitrary entries (like foobar above) from the parent to each child field.

EDIT:

Fields are resolved independently of one another, so there is no mechanism for declaring dependencies between fields. If the getUser resolver is looking at the requested fields and making certain API calls based on requested fields (and omitting others if those fields were not requested), then you'll need to modify that logic to account for the notes field needing the user email.

0
votes

I think the expectation is that if you control the query of the parent, and expect the value in the child, you should ensure the required value is always resolved by the parent.

There is, however, a way to do what you are asking when merging schemas. This is described here https://www.apollographql.com/docs/graphql-tools/schema-stitching.

Basically would need to have a base schema that is something like

type Query {
    getUser(userId: ID!): User
}

type User {
   userId: ID
   email: String
}

With the same resolvers as you have now, and a second schema that is something like

type Note {
    noteId: ID
    text: String
}

extend type User {
    notes: [Note]
}

Along with something like

import { mergeSchemas } from 'graphql-tools';

const finalSchema = mergeSchemas({
    schemas: [userSchema, noteSchema],
    resolvers: {
        User {
            notes: {
                fragment: '... on User { email }',
                resolve: notesResolver
            }
        }
    }
});
console.dir(await graphql(finalSchema, `query { ... }`));

Notice the fragment property defining the email field. In the link above it describes how this will force resolution of the given field on the parent when this child is resolved.