5

I stumbled across an interesting pattern for implementing GraphQL Mutations under their own "namespace", to help keep the Global Namespace neat and tidy. A regular implementation with Apollo would look something like this.

const typeDefs = gql`
type Mutation {
    article: ArticleMutations
  }

  type ArticleMutations {
    like: Boolean
    unlike: Boolean
  }
`;

const resolvers = {
  Mutation: {
    article: () => ({}), // ✨✨✨ magic! which allows to proceed call of sub-methods
  }
  ArticleMutations: {
    like: () => { /* resolver code */ },
    unlike: () => { /* resolver code */ },
  },
};

I decided to try and implement this pattern in a GraphQL API I am building with NestJS, however I seem to be hitting a stumbling block. I have created a TestResolver class, which contains a simple query, along with a namespaced-mutation that should return my nested resolvers.

@ObjectType()
export class TestMutations {
  constructor(private readonly _testService: TestService) {}

  @Field(() => String)
  test: string;

  @ResolveField(() => Unit, {})
  async create(@Args('input') input: CreateTestInput): Promise<Test> {
    try {
      return await this._testService.create(input);
    } catch (err) {
      this._logger.error(err);
      throw new HttpException('An error occurred when creating the Test', HttpStatus.INTERNAL_SERVER_ERROR);
    }
  }
}

@Resolver(() => Test)
export class TestResolver {
  constructor(private readonly _testService: UnitService) {}

  @Query(() => Test, { name: 'test', nullable: true })
  async getTest(@Args('id') id: Id): Promise<Test> {
    try {
      return await this._testService.get(id);
    } catch (err) {
      this._logger.error(err);
      throw new HttpException('An error occurred when fetching the Test', HttpStatus.INTERNAL_SERVER_ERROR);
    }
  }

  @Mutation(() => TestMutations)
  test(): TestMutations {
    return new TestMutations(this._testService);
  }
}

Through some trial and error I managed to get NestJs to successfully build the above code, however I cannot get the nested FieldResolvers to show up.

enter image description here

The plain Apollo implementation of this pattern does seem like it uses some "magic" code to achieve it, so it's definitely possible that the structure of NestJS will not allow me to do something like this. Does anyone have any ideas?

2 Answers 2

5

You can do this with Nest.js too. Here how i did it:

  1. I created root resolver.

@Resolver('root')
export class RootResolver {
  @Query((returns) => KBQuery, { name: 'kb' })
  kbQuery() {
    return new KBQuery();
  }

  @Query((returns) => UserQuery, { name: 'user' }) // Here can be @Mutation
  userQuery() {
    return new UserQuery();
  }
  // ... Other root Queries and Mutations here
}

  1. Then i create child resolvers (for queries or mutations) like this:

@ObjectType()
export class UserQuery {}

@Resolver((of) => UserQuery)
export class UserQueryResolver {
  @ResolveField((returns) => UserMyPayload)
  async me(@Context('req') { currentUser }: Request) {
    if (!currentUser) return { error: new ApiError('unauthorized', 'Authorization required') };

    return { user: currentUser.user };
  }
}

  1. So now i can make query like this: user.me to get user details.

I wish it will be more easier to do with Nest.js. So guys from Nest need to add something in @Query/@Mutation attribute.

Sign up to request clarification or add additional context in comments.

Comments

0

I’ve been following this discussion and ran into the same problem in my own projects — needing a clean way to namespace queries and mutations in NestJS (code-first) without a ton of boilerplate.

To solve this, I recently published a small package: nestjs-gql-namespaces.

It provides three decorators — @NamespaceResolver, @NestedMutation, and @NestedQuery — that let you group operations under namespaces in a type-safe, NestJS-friendly way. Example:

    @NamespaceResolver({ fieldName: 'user' })

    export class UserResolver {
      @NestedMutation()
      createUser(@Args('input') input: CreateUserInput) {
        return this.userService.create(input);
      }

      @NestedQuery()
      profile(@Args('id') id: string) {
        return this.userService.getProfile(id);
      }
    }

This generates a schema like:

    type Mutation {
      user: UserMutations!
    }

    type UserMutations {
      createUser(input: CreateUserInput!): User!
    }

    type Query {
      user: UserQueries!
    }
    
    type UserQueries {
      profile(id: ID!): UserProfile!
    }

1 Comment

As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.