pythondjangographqlgraphene-django

GraphQL Queries Per App in Django (Graphene)


I started moving my REST API endpoints to using GraphQL with Graphene. Seems pretty straightforward so far, but one of the things that I like about the REST API (and I cannot figure out in Graphene) is the structure of "endpoints" for each app. I have a lot of apps in my Django application, and I would like to group the Graphene queries and mutations of each app under a single "endpoint" (just like you would do in REST by sending a request to app_1/endpoint and app_2/endpoint).

Currently I have a graphql folder inside of each app, with files for my queries and mutations inside. Then, under my main schema file, I just create a giant query and mutation objects that inherit from the elements of all other apps.

# app1/graphql/queries.py
class Endpoint1(DjangoObjectType):
    class Meta:
        model = Element1
        fields = ("id", "name", "date")

# app2/graphql/queries.py
class Endpoint2(DjangoObjectType):
    class Meta:
        model = Element2
        fields = ("id", "name", "date")

# Place where my main schema is located
# django_project/graphql/queries.py
class Queries(Endpoint1, Endpoint2):
    pass

Would it be possible to group queries and mutations from a single app and then just inherit from each of the app's mutations and queries in the main schema, and then have the GraphQL request be structured like this?

query {
  app1 {
    endpoint1 {
      id
      name
    }
  }
}

query {
  app2 {
    endpoint2 {
      id
      name
    }
  }
}

With my current approach I currently just get all of the endpoints bunched up into a single set found under query.

query {
  endpoint1 {
    id
    name
  }
  endpoint2 {
    id
    name
  }
}

Solution

  • So, what I did was not go with federation (as I wanted a simple unified API and federation seemed a bit overkill to me), but to simply split the location of schemas in a per-app basis and simply join them in a common query found in the main Django project folder. Here's an example:

    This is for a file found in django_project/graphql/schema.py

    from graphene import ObjectType, Field, Schema
    from graphene_federation import key, build_schema
    
    from app_config_generator.graphql.schema import ConfigGeneratorQuery
    
    # ============================================== #
    # QUERIES                                        #
    # ============================================== #
    
    
    class Query(ObjectType):
        """
        Queries for all apps in Opus
        """
    
        # App: Config Generator
        config_generator = Field(
            ConfigGeneratorQuery,
            name="config_generator",
            description="Models for configuration generators"
        )
    
    # ============================================== #
    # SCHEMA                                         #
    # ============================================== #
    
    
    schema = Schema(query=Query)
    

    This is then exposed to the browser via this config in django_project/urls.py

    from django.urls import path
    from graphene_django.views import GraphQLView
    from django_project.graphql.schema import schema
    
    # ============================================== #
    # BASE ROUTES                                    #
    # ============================================== #
    
    urlpatterns = [
    
        ...
    
        # GraphQL Routes
        path('graphql/', GraphQLView.as_view(graphiql=True, schema=schema)),
    
        ...
    ]
    

    Finally, your app's schemas should look something like this:

    from graphene import ObjectType, List
    
    # Custom Scripts
    from app_config_generator.models import (
        SwitchDaylightSavingsModel,
        GPNSVlan
    )
    from app_config_generator.graphql.fields import (
        GPNSVlanType,
        SwitchDaylightSavingsType
    )
    
    # ============================================== #
    # SUBQUERIES                                     #
    # ============================================== #
    
    
    class SwitchDaylightSavings(ObjectType):
    
        switch_daylight_savings = List(
            SwitchDaylightSavingsType,
            description="Pairs all of the available vendors by their daylight savings regions"
        )
    
        def resolve_switch_daylight_savings(self, info, **kwargs):
            return SwitchDaylightSavingsModel.objects.all()
    
    
    class GPNSVlans(ObjectType):
    
        gpns_vlans = List(
            GPNSVlanType,
            description="All VLANs used by the GPNS standard to configure both firewalls and switches."
        )
    
        def resolve_gpns_vlans(self, info, **kwargs):
            return GPNSVlan.objects.all()
    
    
    # ============================================== #
    # MAIN QUERY                                     #
    # ============================================== #
    
    class ConfigGeneratorQuery(
        SwitchDaylightSavings,
        GPNSVlans,
    ):
        """
        Groups all subqueries for Configuration Generators
        """
        pass
    
    

    With this I was able to call the "SwitchDaylightSavings" mutation, under the "config_generator" route. I don't remember the details correctly, but this should give anyone a good head start. I think there are better and more elegant solutions for this, I especially recommend checking out Strawberry