Why each GraphQL Object type has both "Resolver" and "RelationResolver" types?

For each GraphQL object that’s defined, there are corresponding [ObjectName]Resolver
and [ObjectName]RelationResolver that are generated.

Resolver fields are wrapped with OptArgsResolverFn
and RelationResolver fields are wrapped with RequiredResolverFn.

What is the reason behind having both these types defined?

Thank you

1 Like

I’m curious if this is answered elsewhere? I have a similar question and am not understanding the behavior of these two objects as it relates to a gql query in a Cell.

In my case, I have a new Resolver that I created to handle the inclusion of related models in a return value:

export const technologyModelsWithRelations: QueryResolvers['technologyModelsWithRelations'] =
  ({ ids }) => {
    const technologyModels = db.technologyModel.findMany({
      where: {
        id: {
          in: ids,
        },
      },
      include: {
        technologyModelVersions: {
          orderBy: {
            version: 'desc',
          },
          take: 1,
        },
      },
    })
    return technologyModels
  }

The autogenerated RelationResolver is:

export const TechnologyModel: TechnologyModelRelationResolvers = {
  technologyModelVersions: (_obj, { root }) => {
    return db.technologyModel
      .findUnique({ where: { id: root?.id } })
      .technologyModelVersions()
  },
}

When I run the following in a Cell:

gql`
  query TechnologyModelsWithRelationsQuery($technology_model_ids: [Int!]!) {
    technologyModelsWithRelations(ids: $technology_model_ids) {
      id
      status
      clusterId
      technologyModelVersions {
        id
        version
        name
        description
      }
    }
  }
`
  • the return value includes all of the technologyModelVersions instead of being limited to a single result as defined in technologyModelsWithRelations.
  • If I update the TechnologyModelRelationResolvers definition to take: 1 then it “works” but it is unclear to me how I could then manage a separate query in which I do want to return all of the technologyModelVersions instead of just one?

Many thanks for any additional insight into this or places for me to read up!

Hi @gcallsen and glad to see you trying out RedwoodJS!

You’ve stumbled upon a topic that we should definitely clarify in the docs and that’s the way GraphQL plays with the services.

In Redwood, a service method (e.g. technologyModelsWithRelations) gets automatically mapped to a GraphQL resolver of the same name – and which you had defined in one of your sdl files. Redwood also assembles all the sdl (aka types), the services (aka resolvers), directives, and other things like subscription and such to make a large schema. We let you work on the schema in pieces and “merge:” everything that the GraphQL server function needs.

GraphQL sees queries like

post {
 id
 title  
 authors {
   id
   name
  }
}

as:

  • get me the post
  • get me the post’s authors

It will use the author resolver and the author’s books resolver to fetch that. And yes, this could be two separate queries.

Now, even if in your Prisma query as part of the post services you eager load and include the authors and even sort the authors by name, GraphQL won’t use that info but go back the the post’s authors resolver.

So, an example we can see if that books may not be sorted by title even though the author service is doing that.

Of course, you could replicate the sort logic in the post’s authors resolver … but there’s a better way.

If you eager load and include and sort the authors in your post service, you can “shortcut” in the posts’ authors resolver to ask – do I already have authors populated on my root object (ie, did post eager load and sort the authors I wanted already)? if, so no more work is needed and you can return the root as it doesn’t need to make another db call to fetch the post’s authors.

export const Post: PostRelationResolvers = {
  authors: (_obj, { root }) => {
   // shortcut and see if the root Post already has authors populated, and if so return them
   // if the post service eager loaded and included and sorted, they will retain that filtering
    if (root.authors) {
      return root.authors
   } 
   // otherwise you need to fetch them from the db
    return db.post.findUnique({ where: { id: root?.id } }).authors()
  },
}

We’ve been considering changing this in the default CRUD scaffolded services – and should document better.

However, with RSC and direct db fetching, this behavior will go away since what the service returns is what is returned.

Hope that helps.

I think you could try:

export const TechnologyModel: TechnologyModelRelationResolvers = {
  technologyModelVersions: (_obj, { root }) => {

    if (root.technologyModelVersions) {
      return root.technologyModelVersions
   } 

    return db.technologyModel
      .findUnique({ where: { id: root?.id } })
      .technologyModelVersions()
  },
}

and let your service of the take 1 etc.

Let me know if that works – and if not, we can try something else.

1 Like

Amazing, thank you so much for the detailed/quick feedback! Yes, that does exactly what I expect with your suggested snippet. Lots of concepts to commit to memory but that’s always the case - I am loving working with Redwood.

1 Like

@gcallsen Glad it’s working. It’s a concept I said we haven’t documented or added to the generators well enough. It can help reduce N=1 queries, too, if you eager load common queries.

I often tend to write specific services w/out relation resolvers when I know I’ll have a specific return shape.