Graphql: Feature Request: Resolvers for InterfaceType, again (they landed in type-graphql)

Created on 12 Jun 2020  路  3Comments  路  Source: nestjs/graphql

I'm submitting a...


[ ] Regression 
[ ] Bug report
[x] Feature request
[ ] Documentation issue or request
[ ] Support request => Please do not submit support request here, instead post your question on Stack Overflow.

Current behavior

I'm reopening #429 which was closed and locked.

@ResolveField does not work for @InterfaceType and that's a very serious limitation.

Expected behavior

@ResolveField should be supported for @InterfaceType

type-graphql introduced support for this a while ago, but it seems that in @nestjs/graphql they still don't work.

Relevant PR: https://github.com/MichalLytek/type-graphql/pull/579

From what I understand, @nestjs/graphql forked away from type-graphql.

Minimal reproduction of the problem with instructions

@InterfaceType()
class Foo {
  user: User
}

@ObjectType({ implements: Foo })
class Bar { ... }

@ObjectType({ implements: Foo })
class Baz { ... }

@ObjectType({ implements: Foo })
class Qux { ... }

@Resolver(of => Foo)
class Resolver {
  @ResolveField(type => User)
  user() { ... }
}

The above doesn't work, so you cannot populate user without creating separate resolvers for each object type, and duplicating it in each one.

What is the motivation / use case for changing the behavior?

The motivation has been discussed several times and is mostly related to code reuse, related issues:

429

https://github.com/MichalLytek/type-graphql/issues/260
https://github.com/MichalLytek/type-graphql/issues/261

Most helpful comment

This should be available in 7.5.0 (resolvers for interfaces).

Example:

@Resolver((of) => IRecipe)
export class IRecipesResolver {
  @ResolveField('interfaceResolver', () => Boolean)
  interfaceResolver(
    @Args('arg', { type: () => Number, nullable: true }) arg: number,
  ) {
    return true;
  }
}

where IRecipe is an @InterfaceType().

All 3 comments

I was able to find a workaround, but it isn't pretty:

function ResolverForUser<T extends Type<unknown>>(
  classRef: T,
  fieldName: string,
): any {
  @Resolver(classRef)
  class UserResolver {
    @ResolveField(fieldName, () => User)
    public user(
      @Parent()
      parent: any,
    ) {
      // fetch user
    }

    //@ts-ignore
    static get name() {
      // need to override name to be unique, or resolver does not work
      return `${classRef.name}Resolver`;
    }
  }

  return UserResolver;
}


export const UserResolvers = [
  Bar,
  Baz,
  Qux,
].map(type => ResolverForUser(type, "user"));

// then supply it to module providers like
@Module({
  ...,
  providers: [...UserResolvers],
}

Hey everyone, just throwing my 2 cents in.

In addition to reducing the amount of duplicated code, this is also important because there is currently no way to define arguments on interface fields.

An implementation of this could use the interfaces metadata to find each type that implements the interface. Then it could register the resolver for each field in the implementing types, that don't already have a resolver for that field.

@kamilmysliwiec What's your opinion? Is there a better way to do this? A workaround that doesn't require changing the lib?

This should be available in 7.5.0 (resolvers for interfaces).

Example:

@Resolver((of) => IRecipe)
export class IRecipesResolver {
  @ResolveField('interfaceResolver', () => Boolean)
  interfaceResolver(
    @Args('arg', { type: () => Number, nullable: true }) arg: number,
  ) {
    return true;
  }
}

where IRecipe is an @InterfaceType().

Was this page helpful?
0 / 5 - 0 ratings