1
votes

I am using Apollo Server v2 for my project

I have added auth like given here https://www.apollographql.com/docs/apollo-server/features/authentication/

I wanted to include nested resolver in my Query and Mutation so I did as per https://stackoverflow.com/a/40916089/7584077

The thing is my resolver is a lil bit more complex than the one shown above

// typeDefs/typeDefs.js
import { gql } from "apollo-server-express";
import issueTracker from "./issueTracker";

const base = gql`
  scalar Timestamp

  type Query {
    ping: String!
  }

  type Mutation {
    ping: String!
  }
`;

export default [base, issueTracker];
// typeDefs/issuetracker.js
import { gql } from "apollo-server-express";

export default gql`
  type Comment {
    id: ID
    message: String
    commentBy: User
    createdAt: Timestamp
    updatedAt: Timestamp
    version: Int
  }

  type Issue {
    id: ID
    requestId: ID
    title: String
    issueNumber: Int
    status: Int
    tags: [String]
    assignees: [User]
    createdBy: User
    comments: [Comment]
    createdAt: Timestamp
    updatedAt: Timestamp
    version: Int
  }

  input CreateIssueRequest {
    requestId: ID!
    title: String!
    createdBy: ID!
    message: String!
    assignees: [ID]!
  }

  type IssueTrackerQuery {
    ping: String!
  }

  type IssueTrackerMutation {
    createIssue(request: CreateIssueRequest!): Issue
  }

  extend type Query {
    IssueTracker: IssueTrackerQuery
  }

  extend type Mutation {
    IssueTracker: IssueTrackerMutation
  }
`;

And a lil modified version of the stackoverflow answer above. Here is my combined resolver.

// resolvers/resolvers.js
import IssueTracker from "./issueTracker";

export default {
  Query: {
    ping: () => "ping!",
    IssueTracker: () => ({
      ping: IssueTracker.ping,
    }),
  },
  Mutation: {
    ping: () => "ping!",
    IssueTracker: () => ({
      createIssue: IssueTracker.createIssue,
    }),
  },
};

This is because I wanted Query & Mutation to be completely separate.

Here is my IssueTracker resolver

// resolvers/issueTracker.js
export default {
  ping: () => "ping",
  createIssue: async (parent, args, context) => {
    console.log(parent);
    console.log(args);
    console.log(context);
    // create issue as per request and return
  }

The thing is here is that, parent actually is the args field! And I need userId from context to make sensible data.

1

1 Answers

0
votes

Hmm, the SDL first approach can be a bit tricky. It's not easy to explain what is wrong here but I will do my best. First let me tell you what to need to do to make this work and then I will explain what goes wrong.

Create a IssueTrackerMutation field in the resolver map:

export default {
  Query: {
    ping: () => "ping!",
    IssueTracker: () => ({ // same here but I will just do the mutation for you
      ping: IssueTracker.ping,
    }),
  },
  Mutation: {
    ping: () => "ping!",
    IssueTracker: () => null, // or whatever you want as a root here
  },
  IssueTrackerMutation: {
    createIssue: IssueTracker.createIssue
  }
};

Note the difference between creating a "pure" resolver for the IssueTracker and returning an object for IssueTracker with a createIssue method.

Now the function should be called with the expected parameters. The reason why the parent argument seems to be missing is the very special implementation of the default resolver. The resolver is intended to work with an object oriented style where fields can be fields or methods. You can imagine the resolver to work like this:

defaultResolver(fieldName, parent, args, context, info) {
  if (typeof parent !== 'object') {
    throw "Need object to default resolve";
  }
  if (typeof parent[fieldName] === 'function') {
    return parent[fieldName](args, context, info);
  }
  return parent[fieldName];
}

This would allow you to write your data access objects in the following manner:

class IssueTracker {
  issues = []
  async createIssue(args, ctx, info) {
    const issue = await createIssue(args.request);
    this.issues.push(issue);
    return issue;
  }
}

const issueTracker = new IssueTracker();

export default {
  // ...
  Mutation: {
    // ...
    IssueTracker: () => issueTracker,
  },
  IssueTrackerMutation: {
    createIssue: IssueTracker.createIssue
  }
};

This is not talked about that much but is probably relatively close how GraphQL at Facebook works. The community seems to move more logic into the resolvers.