Filter Embedded Document List in GraphQL

Viewed 416

I'm building a GraphQL application in Python/Graphene using a MongoDB backend (through MongoEngine). Everything has been working well, but noticed that there's not a lot documentation for handling nested lists of embedded documents. I thought one power of GraphQL was the ability to project only the properties you want, but it doesn't appear to be the case fully.

Looking at this collection as an example:

[
  {
    "name": "John Doe",
    "age": 37,
    "preferences": [
      {
        "key": "colour",
        "value": "Green"
      },
      {
        "key": "smell",
        "value": "onions cooking in butter"
      },
      ...
    ]
  },
  ...
]

If I want to find a particular object through GraphQL, I would look up through a query like

{
    person(name: "John Doe"){edges{node{
      name age preferences{edges{node{
        key value
      }}}
    }}}
}

But this could bring back hundreds of nested documents. What I would like to do instead is to identify the requested nested documents as part of the projection request.

{
    person(name: "John Doe"){edges{node{
      name age preferences(key: "colour"){edges{node{
        key value
      }}}
    }}}
}

My understanding reading the GraphQL spec is these sub-queries are not possible, but wanted to confirm with experts first. And if it is possible, how would I implement it to support these types of requests?

Update Maybe a schema example will provide some more insightful responses.

class PreferenceModel(mongoengine.EmbeddedDocument):
    key = mongoengine.fields.StringField()
    value = mongoengine.fields.StringField()


class Preference(graphene_mongo.MongoengineObjectType):
    class Meta:
        interfaces = (graphene.relay.Node, )
        model = PreferenceModel


class PersonModel(mongoengine.Document):
    meta = {'collection': 'persons'}
    name = mongoengine.fields.StringField()
    age = mongoengine.fields.IntField()
    preferences = mongoengine.fields.EmbeddedDocumentListField(PreferenceModel)


class Person(graphene_mongo.MongoengineObjectType):
    class Meta:
        interfaces = (graphene.relay.Node, )
        model = PersonModel


class Query(graphene.ObjectType):
    person = graphene_mongo.MongoengineConnectionField(Person)

schema = graphene.Schema(query=Query, types=[Person])
app = starlette.graphql.GraphQLApp(schema=schema)

Using this above structure, what changes would be necessary to allow for queries/filters on nested objects?

1 Answers

I had a similiar issue but working graphene-django. I solved it using custom resolvers on the DjangoObjectType, like this:


import graphene
from graphene_django import DjangoObjectType
from .models import Question, Choice, SubChoice


class SubChoiceType(DjangoObjectType):
    class Meta:
        model = SubChoice
        fields = "__all__"


class ChoiceType(DjangoObjectType):
    sub_choices = graphene.List(SubChoiceType, search_sub_choices=graphene.String())

    class Meta:
        model = Choice
        fields = ("id", "choice_text", "question")

    def resolve_sub_choices(self, info, search_sub_choices=None):
        if search_sub_choices:
            return self.subchoice_set.filter(sub_choice_text__icontains=search_sub_choices)

        return self.subchoice_set.all()


class QuestionType(DjangoObjectType):
    choices = graphene.List(ChoiceType, search_choices=graphene.String())

    class Meta:
        model = Question
        fields = ("id", "question_text")

    def resolve_choices(self, info, search_choices=None):
        if search_choices:
            return self.choice_set.filter(choice_text__icontains=search_choices)

        return self.choice_set.all()


class Query(graphene.ObjectType):
    all_questions = graphene.List(QuestionType, search_text=graphene.String())
    all_choices = graphene.List(ChoiceType, search_text=graphene.String())
    all_sub_choices = graphene.List(SubChoiceType)

    def resolve_all_questions(self, info, search_text=None):
        qs = Question.objects.all()
        if search_text:
            qs = qs.filter(question_text__icontains=search_text)

        return qs

    def resolve_all_choices(self, info, search_text=None):
        qs = Choice.objects.all()
        if search_text:
            qs = qs.filter(choice_text__icontains=search_text)

        return qs

    def resolve_all_sub_choices(self, info):
        qs = SubChoice.objects.all()
        return qs


schema = graphene.Schema(query=Query)

you can find the example here: https://github.com/allangz/graphene_subfilters/blob/main/mock_site/polls/schema.py

It may work for you

Related