Skip to content

Instantly share code, notes, and snippets.

@reggi
Last active July 10, 2023 14:11
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save reggi/3d7db7693f2e4a327af59df61acb8a92 to your computer and use it in GitHub Desktop.
Save reggi/3d7db7693f2e4a327af59df61acb8a92 to your computer and use it in GitHub Desktop.
How do I have nested resolvers in nestjs / type-graphql?

I am trying to find a way that a resolver can essentially return another resolver using nest.js for creating Resolvers, and type-graphql to create object types.

Here's an example with the star-wars graphql api

{
  allVehicles(first: 1, last: 100) {
    vehicles {
      name
      id
      filmConnection(first: 1, last: 100) {
        films {
          title
          characterConnection(first: 1, last: 100) {
            characters {
              name
            }
          }
        }
      }
    }
  }
}

As you can see above the vehicles type returns a filmConnection which allows you to pass in arguments. In type-graphql, would this be apart of the @ObjectType would it be defined in the @Resolver? Or does it need to be defined in some other way?


Failed Attempts

I set up an example of a couple of nested objects.

Which should look something like this as a query:

Note there are no arguments to these types, that could be optional, but I'd like the resolvers to pass along parent information.

{
  human {
    head {
      eyes {
        color
      }
    }
    body {
      neck
    }
  }
}

Here are the @ObjectTypes

@ObjectType()
class Eyes {
    @Field(type => String)
    color: string
}

@ObjectType()
class Head {
    @Field(type => Eyes, { nullable: true })
    eyes?: Eyes
}

@ObjectType()
class Body {
    @Field(type => String)
    neck: string
}

@ObjectType()
class Human {
    @Field(type => String, { nullable: true })
    name: string

    @Field(type => Head, { nullable: true })
    head?: Head

    @Field(type => Body, { nullable: true })
    body?: Body
}

First Approach, multiple Resolvers

@Resolver(type => Human)
export class HumanResolver {

    @Query(type => Human)
    human(): Human {
        console.log('a')
        return { name: 'Greg' }
    }

}

@Resolver(type => Head)
export class HeadResovler {

    @Query(type => Head)
    head(): Head {
        console.log('B')
        return {}
    }

}

@Resolver(type => Body)
export class BodyResolver {

    @Query(type => Body)
    body(): Body {
        console.log('C')
        return { neck: '102' }
    }

}

@Resolver(type => Eyes)
export class EyesResolver {

    @Query(type => Eyes)
    eyes(): Eyes {
        console.log('d')
        return { color: 'brown' }
    }

}

First Approach, One Resolvers, using @ResolveProperty

Here's another way to do it possibly, with one top level resovler and the rest are properties.

@Resolver(type => Human)
export class BeingResolver {

    @Query(type => Human)
    human(): Human {
        console.log('a')
        return { name: 'Greg' }
    }

    @ResolveProperty(type => Head)
    head(): Head {
        console.log('b')
        return {}
    }

    @ResolveProperty(type => Body)
    body(): Body {
        console.log('c')
        return { neck: '102' }
    }

    @ResolveProperty(type => Eyes)
    eyes(): Eyes {
        console.log('d')
        return { color: 'brown' }
    }
}

The main issue I see here is that @ResolveProperty only goes one level deep.

@reggi
Copy link
Author

reggi commented Nov 28, 2019

Here's a working example:

@ObjectType()
class Level1 {
    @Field(() => String)
    id: string;

    @Field(() => Level2)
    getLevel2Property: Promise<Level2>;
}
@ObjectType()
class Level2 {
    @Field(() => String)
    id: string;

    @Field(() => Level3)
    getLevel3Property: Promise<Level3>;
}
@ObjectType()
class Level3 {
    @Field(() => String)
    id: string;
}
@Resolver(Level1)
export class Level1Resolver {

    @Query(() => Level1, {
        name: 'Level1',
    })
    public async getLevel1(
        @Args('id') id: string
    ): Promise<Level1> {
        const level1 = new Level1();
        level1.id = id;
        return level1;
    }

    @ResolveProperty(() => Level2, {
        name: 'Level2',
    })
    public async getLevel2Property(
        @Args('id') id: string,
        @Parent() parent: Level1
    ): Promise<Level2> {
        const level2 = new Level2();
        level2.id = id;
        return level2;
    }
}
@Resolver(Level2)
export class Level2Resolver {

    @ResolveProperty(() => Level3, {
        name: 'Level3',
    })
    public async getLevel3Property(
        @Args('id') id: string,
        @Parent() parent: Level2
    ) {
        const level3 = new Level3();
        level3.id = id;
        return level3;
    }
}
query Levels {
  Level1(id: "1") {
    id
    getLevel2Property(id: "2") {
      id
      getLevel3Property(id: "3") {
        id
      }
    }
  }
}

@shriomtri
Copy link

shriomtri commented Aug 19, 2022

This is not working as expected.

getLevel3Property throws error. Does not take any argument but in resolved still requires it.

@heji-staizen
Copy link

Thanks @reggi , If only I had seen this much earlier, it could have saved me a lot of time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment