Search code examples
graphqlnestjscode-first

Nested field resolvers in GraphQL


The goal is to use NestJS to implement a GraphQL schema using the code-first approach.

Let's say I have a Pet type in my GraphQL schema with two fields, name and age. If those two pieces of information come from different sources of truth (and I don't always want to fetch both), I could implement a PetResolver class with resolvers for each field:

@Resolver(() => Pet)
export class PetResolver {
  @Query(() => Pet)
  pet(): Pet {
    return {};
  }

  @ResolveField()
  name(): Promise<string> {
    return Promise.resolve('Spot');
  }

  @ResolveField(() => Int)
  age(): Promise<number> {
    return Promise.resolve(2);
  }
}

which could be used like this:

query GetPet {
  pet {
    name
  }
}

This works and would ensure that the value of each field is only fetched when requested, but what if I wanted to have a pet field on my User type that I could query like this:

query GetUserWithPet {
  currentUser {
    email
    pet {
      name
    }
  }
}

Applying the same principle, I could create a UserResolver class like this:

@Resolver(() => User)
export class UserResolver {
  @Query(() => User)
  @UseGuards(AuthCognitoGuard)
  currentUser(@CurrentUser() user: IdTokenPayload): User {
    return {
      id: user.sub,
      email: user.email,
    };
  }

  @ResolveField()
  pet(@Parent() user: User): Promise<Pet> {
    return petService.getPetForUserId(user.id);
  }
}

but then the PetService implementation would have to be aware of which fields were requested if it only wanted to fetch relevant data.

A) Is there a way to use PetResolver within UserResolver to make use of the individual field resolution logic?

B) If not, what is the best way to determine which fields were requested in the query using NestJS code-first conventions?

C) Is this the "wrong" way to think about GraphQL queries? Do best practices dictate that I keep the separate resolver and use a query like this:

query GetUserWithPet {
  currentUser {
    email
  }
  pet {
    name
  }
}

Solution

  • User should contain some petIds [array] value (internal, DB stored field/column) ...

    ... making possible to resolve pets: [Pet] prop/relation - list of Pet ...

    ... like starshipIDs explained in https://graphql.org/learn/execution/

    Notice: pets service is asked about records using pet ids.

    ... but of course pet can contain some ownerId (only or explicitely visible, DB stored field/column) making possible to resolve owner: User prop [reverse] relation - this way you can:

    query PetWithOwner {
      pet (id: "someID") {
        id
        name
        owner {
          id
          email
          # more pets?
          pets {
            id
            name
            # you can loop it ;)
            owner {
              id
              email
    

    pet.owner field resolver can return only { id: ownerId } object (partial response) ... server will try to resolve 'missing' (required by query) email prop using User (owner is User type) type resolver, passing id as an arg (check/console.log parent and args resolver args). You don't have to do it [the same] 'manually' inside pet.owner field resolver.

    Query required fields ...

    ... [selection set] can be read from info object - 4th resolver arg - read docs/tutorial for details