Defining the schema

GraphQL requires a graph-like schema to query against. The nodes and edges of the graph will be the objects and relationships in your API.

Using the Star Wars example from the GraphQL documentation, let’s assume we have a Django app with the following model structure:

  • Characters appear in Episodes.
  • Characters are friends with other Characters.

Adding nodes - Objects

Create an Object node for each of the models:

from django_graph_api import (
    Object,
)

class Episode(Object):
    ...

class Character(Object):
    ...

Add scalar fields to each of the nodes:

from django_graph_api import (
    Object,
    CharField,
    IntegerField,
)

class Episode(Object):
    name = CharField()
    number = IntegerField()

class Character(Object):
    name = CharField()

You can define any field on the node (Object) that is also a field or property of the model that it represents. You can also define custom logic to get a field’s value by adding a get_<field_name> method to the object. The current model instance will be available as self.data.

Arguments can be defined for fields by passing in a dictionary like {'<argname>': <graphql type instance>}. The value passed in a query will be available as a keyword argument to the object’s get_<fieldname> method.

from django_graph_api.graphql.types import Boolean

class Character(Object):
    name = CharField(arguments={'upper': Boolean()})

    def get_name(self, upper=False):
        name = '{} {}'.format(
            self.data.first_name,
            self.data.last_name,
        )
        if upper:
            return name.upper()
        return name

You may also define descriptions for fields as a keyword argument:

class Character(Object):
    name = CharField(description="The name of a character.")

Descriptions defined on a field will appear under the field name in the GraphiQL interactive documentation.

Scalar field types

For scalar types, the type of the field determines how it will be returned by the API.

For example, if a model’s field is stored as an IntegerField on the Django model and defined as a CharField in the graph API, the model value will be coerced from an int to a str type when it is resolved.

Supported scalar types can be found in the API documentation and feature list.

Adding edges - Relationships

In order to traverse the nodes in your graph schema you need to define relationships between them.

This is done by adding related fields to your Object nodes. These non-scalar fields will return other objects or a list of objects.

  • If the field should return an object, use RelatedField
  • If the field should return a list of objects, use ManyRelatedField

When defining the object type of the related field, you can use:

  • The class of the object, e.g. appears_in = ManyRelatedField(Episode)
  • A callable that returns the class of the object, e.g. characters = ManyRelatedField(lambda: Character)
  • ‘self’, when you are referencing the current class, e.g. mother = RelatedField('self')
  • The full path to the class of the object as a string, e.g., appears_in = ManyRelatedField('test_app.schema.Episode')

You can define any related field on the node (Object) that is also a field or property of the model that returns another model, list of models, or model manager. You can also define custom logic by adding a get_<field_name> method to the object. The current model instance will be available as self.data.

Examples

Many-to-many relationship

from django_graph_api import (
    ManyRelatedField,
)

class Episode(Object):
    characters = ManyRelatedField(lambda: Character)

class Character(Object):
    appears_in = ManyRelatedField(Episode)

Many-to-one relationship

from django_graph_api import (
    ManyRelatedField,
    RelatedField,
)

class Character(Object):
    mother = RelatedField('self')
    children = ManyRelatedField('self')

One-to-one relationship

from django_graph_api import (
    RelatedField,
)

from .models import {
    Episode as EpisodeModel
}

class Episode(Object):
    next = RelatedField('self')
    previous = RelatedField('self')

    def get_next(self):
        return EpisodeModel.objects.filter(number=self.data.number + 1).first()

    def get_previous(self):
        return EpisodeModel.objects.filter(number=self.data.number - 1).first()

Defining query roots

By defining query roots, you can control how the user can access the schema. You can pass a single query root or an iterable of query roots to a schema on instantiation. Passing multiple query roots is the recommended method for setting up decoupled apps within a project.

from django_graph_api import RelatedField
from .models import Character as CharacterModel
from .models import Episode as EpisodeModel

class QueryRoot(Object):
    hero = RelatedField(Character)

    def get_hero(self):
        return CharacterModel.objects.get(name='R2-D2')

schema = Schema(QueryRoot)

# Or:

schema = Schema([EmailQueryRoot, BlogQueryRoot])

Note

Field names on query roots must be unique within a schema instance. A query root that declares a particular field will also be responsible for resolving it.

Sample queries

You should now be able to create more complicated queries and make use of GraphQL’s nested objects feature.

{
  episode(number: 4) {
    name
    number
    characters {
      name
      friends {
        name
      }
    }
  }
}