FuzzyTemper
FuzzyTemper

Reputation: 783

Use the same class as Input and Object type in GraphQL in NestJS

I am trying to setup my graphql resover to handle an array of objects but cant get the @Args decorator configured.

I created my own ArgsType

import { ArgsType, Field, Int, ObjectType } from '@nestjs/graphql';

@ArgsType()  // to be used as type in the resolver
@ObjectType()  // for schema generation 
export class Club {
 @Field(type => String)
 president: string;

 @Field(type => Int)
 members?: number;
}

Resolver with adding a single Club works just fine!

 @Query(() => Int)
 async addClub(@Args() club: Club) {
  // handle stuff
  }

but if I want to give an array of Club like this

 @Query(() => Int)
   async addClubs(@Args({name: 'clubs', type: () => [Club]}) clubs: Array<Club>) {
   // handle stuff
  }

this thows an error when nest is starting up

 UnhandledPromiseRejectionWarning: Error: Cannot determine a GraphQL input type for the "clubs". Make sure your class is decorated with an appropriate decorator.

although I am able to use an array of Strings like this

 @Query(() => [String])
 async addStrings(@Args({ name: 'clubs', type: () => [String], }) clubs: Array<string>) {
  // handle stuff
  }

I am pretty sure there should be an easy solution, but cant figure out where to go from here.

Upvotes: 20

Views: 13357

Answers (1)

Eranga Heshan
Eranga Heshan

Reputation: 5804

According to the error,

Cannot determine a GraphQL input type for the "clubs". Make sure your class is decorated with an appropriate decorator

You're trying to use Club class as a GraphQL input type while it is already an object type (According to @ObjectType annotation you use).


Solution 1:

I would suggest you write a separate GraphQL input type like below (not tested). This is cleaner and less coupled if you need to separate the way you treat input and output of Club.

import { InputType, Field } from '@nestjs/graphql';

@InputType()
export class ClubInput {
 @Field()
 president: string;

 @Field()
 members?: number;
}

Then in your resolver, you can use it like below.

@Query(() => Int)
async addClubs(@Args({name: 'clubs', type: () => [ClubInput]}) clubs: Array<ClubInput>) {
 // handle stuff
}

Solution 2:

But if you really need to use the same class for both purposes, you could try to create both input and object types with the same Club name. The name of the object type is by default the name of the class. So you need to provide the name explicitly to avoid conflict.

import { Field, Int, ObjectType, InputType } from '@nestjs/graphql';

@InputType("ClubInput")
@ObjectType("ClubType")
export class Club {
 @Field(type => String)
 president: string;

 @Field(type => Int)
 members?: number;
}

Now your Club has different names for input and object types. Then in your resolver, you can use it like below.

@Query(() => Int)
async addClubs(@Args({name: 'clubs', type: () => [ClubInput]}) clubs: Array<ClubInput>) {
 // handle stuff
}

Note: In this solution, you need to make sure that Club will never contain fields that express circular references or references to interfaces and unions. If that is going to happen, you will have to move to Solution 1, else this will make your input throw an error.


The take away is that in Typescript GraphQL, InputType and ObjectType are two different concepts and we need to use it properly to avoid any issues.

Upvotes: 29

Related Questions