graphqlnestjsapolloresolver

NestJS GraphQL federation circular resolvers


I am working on an existing GraphQL service, that I have successfully broken down to smaller services, using apollo federation. I have some types being extended by other services and everything works just fine. However, as I followed this example: https://docs.nestjs.com/graphql/federation now I have a circular reference kind of problem.

So basically I have, for example two types:

@ObjectType()
@Directive('@key(fields: "id")')
export class Original {
    @Field(type => ID)
    id: string;

    ...
}

// extending it in the other service
@ObjectType()
@Directive('@extends')
@Directive('@key(fields: "id")')
export class Original {
    @Field(type => ID)
    @Directive('@external')
    id: string;

    @Field(type => [Other])
    @Directive('@requires(fields: "id")')
    others: Other[];

    ...
}

@ObjectType()
@Directive('@key(fields: "id")')
export class Other {
    @Field(type => ID)
    id: string;
    
    ...

    @Field(type => Original, { nullable: true })
    original?: Original;
}

And I have two resolvers, both in the service extending the original type:

@Resolver(of => Original)
export class OriginalResolver {
  ...

  @ResolveField(returns => [Other])
  async others(@Parent() original: Original) {
      const { id} = original;
      ...
  }
}

@Resolver(of => Other)
export class OtherResolver {
  ...
  @ResolveField((of) => Original)
  async original(@Parent() other: Other) {
    return { __typename: 'Orignal', id: other.original.id };
  }
}

As the resolvers suggest, I can have a query with something like this:

...,
original{
  others{
    original{
      *and so on...*
    }
  }
}

I don't want this circular query to be possible and I am trying to remove it, but so far I had no luck. If I simply remove the "original" field resolver, where it should return the __typename, apollo just won't extend the original type anymore. I guess that line is what basically connects the two services to find the original type, but I am not that deep in apollo so far...

So my question is how could I remove that resolver all together OR if that just has to be there for apollo to work, is there any way to "hide it"?

Thanks in advance and feel free to ask for any more info you might need.


Solution

  • It's fully legal to have 'loops' in GraphQL (notice 'graph'). GraphQL 'by design' gives the ability to freely shape the query [and structure of the response] including 'loops' creation.

    I wouldn't say it's 'circular reference kind of problem'. It can be an efficiency/performance problem ... **This is not an evil ... when not abused.

    You can use some metrics to limit API usage, restrict 'max resolving depth level'/etc.

    In this case, you can simply return null in original resolver when parent is others type. This way original can be queried only on query top/root level.