Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Apollo GraphQL server: filter (or sort) by a field that is resolved separately

I might be facing a design limitation of Apollo GraphQL server and I'd like to ask if there is a workaround.

My schema contains type Thing, that has field flag. I'd like to be able to filter things by the value of flag, but there is appears to be impossible if this field is resolved separately. The same problem would arise if I wanted to sort things. Here’s an example:

  type Thing {
    id: String!
    flag Boolean!
  }

  type Query {
    things(onlyWhereFlagIsTrue: Boolean): [Thing!]!
  }
const resolvers = {
  Thing: {
    flag: async ({id}) => {
      const value = await getFlagForThing(id);
      return value;
    }
  },
  Query: {
    async things(obj, {onlyWhereFlagIsTrue = false}) {
      let result = await getThingsWithoutFlags();
      if (onlyWhereFlagIsTrue) {
        // ↓ this does not work, because flag is still undefined
        result = _.filter(result, ['flag', true]);
      }
      return result;
    }
  }
}

Is there any way of filtering things after all the async fields are resolved? I know I can call getFlagForThing(id) inside things resolver, but won't that be just repeating myself? The logic behind resolving flag can be a bit more complex than just calling one function.

UPD: This is the best solution I could find so far. Pretty ugly and hard to scale to other fields:

const resolvers = {
  Thing: {
    flag: async ({id, flag}) => {
      // need to check if flag has already been resolved
      // to avoid calling getThingsWithoutFlags() twice
      if (!_.isUndefined(flag)) {
        return flag;
      }
      const value = await getFlagForThing(id);
      return value;
    }
  },
  Query: {
    async things(obj, {onlyWhereFlagIsTrue = false}) {
      let result = await getThingsWithoutFlags();
      if (onlyWhereFlagIsTrue) {
        // asynchroniously resolving flags when needed
        const promises = _.map(result, ({id}) =>
          getFlagForThing(id)
        );
        const flags = await Promise.all(promises);
        for (let i = 0; i < flags.length; i += 1) {
          result[i].flag = flags[i];
        }
        // ↓ this line works now
        result = _.filter(result, ['flag', true]);
      }
      return result;
    }
  },
};
like image 453
Alexander Kachkaev Avatar asked Nov 07 '22 15:11

Alexander Kachkaev


1 Answers

I think that the issue here is not really a limitation of Apollo server, and more to do with the fact that you have a primitive field with a resolver. Generally, it's best to use resolvers for fields only when that field is going to return a separate type:

Thing {
    id: ID!
    flag: Boolean!
    otherThings: OtherThing
}

Query {
    things(onlyWhereFlag: Boolean): [Thing!]!
}

In this example, it would be fine to have a separate resolver for otherThings, but if a field is a primitive, then I would just resolve that field along with Thing.
Using your original schema:

const filterByKeyValuePair = ([key, value]) => obj => obj[key] === value;

const resolvers = {
    Query: {
        async things(parent, { onlyWhereFlag }) {
            const things = await Promise.all(
                (await getThings()).map(
                    thing =>
                        new Promise(async resolve =>
                            resolve({
                                ...thing,
                                flag: await getFlagForThing(thing)
                            })
                        )
                )
            );

            if (onlyWhereFlag) {
                return things.filter(filterByKeyValuePair(['flag', true]));
            } else {
                return things;
            }
        }
    }
};

What if flag wasn't a primitive? Well, if you want to filter by it, then you would have a couple of different options. These options really depend on how you are fetching the "flag" data. I'd be happy to elaborate if you can provide more details about your schema and data models.

like image 157
davidmwhynot Avatar answered Nov 25 '22 14:11

davidmwhynot