graphql: @Args() in Fields not supported after migrating from type-graphql

I’m submitting a…


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

Current behavior

In type-graphql it was possible to do this: https://graphql.org/learn/schema/#arguments

@ArgsType()
export class FilterJobResultsArgs {
  @Field(type => JobContentTypeEnum, { nullable: true })
  type?: JobContentTypeEnum | null;

  @Field(type => JobContentStatusEnum, { nullable: true })
  status?: JobContentStatusEnum | null;
}

@ObjectType()
export class MyJob {
  @Field(type => Number)
  jobResultsCount(@Args() filters: FilterJobResultsArgs) {
    return this.jobResults(filters).length;
  }
}

This would output to the GraphQL schema


enum JobContentStatus {
  Completed
  Errored
}


enum JobContentType {
  image
  video
}

type MyJob {
     jobResultsCount(type: JobContentType, status: JobContentStatus): Float!
}

But instead, the output is:

enum JobContentStatus {
  Completed
  Errored
}

enum JobContentType {
  image
  video
}

type MyJob {
     jobResultsCount: Float!
}

This makes it impossible to upgrade past Nest 7.x at this time.

Environment


Nest version: 8.0.1

 
For Tooling issues:
- Node version: 12
- Platform: Mac

Others:

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 1
  • Comments: 16 (10 by maintainers)

Most upvoted comments

@elie222 Args work fine for @Query methods but if you have a field that requires args or you have a @ResolveField decorator on the resolver, arguments are not picked up.

It looks like https://github.com/nestjs/graphql/blob/master/lib/schema-builder/factories/object-type-definition.factory.ts#L87 does not even consider args.

Actually, it was an “accident” (not planned side-effect) that field resolvers defined as methods within object types worked in the past. Any Nest feature, in fact, wasn’t supported there, including guards, interceptors, filters, pipes (validation & transformation - invalid arguments were accepted), and custom decorators. Everything worked “well” unless you tried to use any framework feature. Nest cannot create the execution context outside of the DI IoC (for classes not defined as either providers or controllers), and therefore it’s pretty much impossible for us to enable such a feature (unless we decide to force people to define object types as providers, but this would bring even more side-effects because providers can be, for example, request-scoped + they can also be injected into other providers which in case of models doesn’t make any sense).

Hence, any logic that requires dealing with arguments should be located inside the resolver class. If you want to isolate some domain/business logic and put it into the object types (which is completely fine), simply define a normal public method and call this method from within the @ResolveField after extracting/processing arguments and that’s it. The fact that you must use @ResolveField() instead of defining a method decorated with @Field() doesn’t mean that you have to put the entire logic in there. You can still isolate domain-specific logic and call it from the resolver class. The only limitation here is that you can’t use @Args decorator in the ObjectType class, but the rest of your code can remain in the object class.

@j I agree. This really does make following the code paths easier. I wish it would be reconsidered.

@kamilmysliwiec Yeah, @ResolveField is what I had to add which just calls the same method on the model. Getters and setters (as in getFoo()/setFoo()) is a known coding style for models. I get the whole “no business logic” in models, but sometimes shared business logic is needed. I happen to use these methods I want as GraphQL fields in various areas.

It “cleans” the models, but then brings a few "WTF"s to how a field is there when it really belongs on a model in our code-base. I’ve always used @FieldResolvers in type-graphql for relations and stuff that is not part of the model. But I feel that it’s perfectly fine to have getX methods with arguments in a model, as long as it’s something simple. My case is it’s just filtering an array.

My Auth models have a hasRole(role: UserRole) method that I want to expose. You already support get foo(), which I use as well, which people put business logic into these types of methods as well.

Thankfully our stuff is fully tested so I can figure out exactly which fields I have to move into the resolvers for now, but for people coming from type-graphql without a test suite… good luck!