Django + Graphene: Intro to the Basics

Written by Gerson Yarce, Software Engineer

There are a lot of misconceptions about using Django and Graphene. For example, many people believe if you want to use GraphQL you need to migrate your whole Django backend to Node. The truth is not only can you use GraphQL on top of your current Django app but there are ways to improve the whole process.


The following is a three-part series on using Graphene with Django:


  1. Django + Graphene: Intro the basics.
  2. Django + Graphene: Testing GraphQL and REST endpoints in Django.
  3. Django + Graphene: Authorization, Pagination, Filtering

I want to use GraphQL but...


For many developers, GraphQL seems intimately tied to Javascript. For example, most GraphQL tutorials and guides require you to set up an Express to start consuming endpoints. Of course, we can do it like that but it’s a lot of unnecessary work. The reality is GraphQL is not a Javascript-only library, but a query language that lets you get the data that you requested. The GraphQL-Express integration is the most popular but integrations exist for many different languages. For example,


Enter Graphene...


I’ve always loved Django. It was the first framework I worked with and helped me a lot in FSL. When GraphQL came out, I was blown away and wondered how I could integrate it with Django. You can start consuming Django REST endpoints under an Express server, but it just doesn’t feel right having two servers consuming so many resources. In order to be more flexible with my data responses, I decided to try Graphene.


Graphene is a powerful library that provides an extendable API, making it easier to integrate your current codebase. You could use graphene-python with your stack right out of the box. But today we will be working with its Django integration, which will give us some additional abstractions to work with.


Getting Started


To install the library in your project, type:

    
pip install graphene-django
    
  

Note: in our requirements.txt we must set the version number the library has, to avoid introducing weird bugs with future versions.


Next, we create a new app where we are going to set up our graph and proceed to enable these apps in our current project.

    
django-admin startapp graph
    
  
    
INSTALLED_APPS = [
    ...
    'django.contrib.staticfiles', # Required for GraphiQL
    'graphene_django',
    'graph'
]
    
  

Once we get this done, let’s enable our route to the GraphQL endpoint in the main app urls.py. (You can disable the graphiql client by setting it with False but we’ll be using it in this example.)

    
# urls.py

from django.urls import path
from graphene_django.views import GraphQLView

urlpatterns = [
    # ...
    path("graphql", GraphQLView.as_view(graphiql=True)),
]
    
  

Let’s add the path to our schema in our settings.py:

    
GRAPHENE = {
    'SCHEMA': 'graph.schema.schema'
}
    
  

Even though we’ve set up our path, we’re still not ready to start using our GraphQL endpoint. How do we set it? Let’s go over the basics!


Schema


Graphene provides us with a schema object attached with resolve methods to fetch data. In Django, we are accustomed to creating apps according to functionality; this structure allows us to create more order and readability in our code. In our demo repository, we have already set up two apps, players and games. So, for these apps, we need to create a schema.py file.


The [player|games]/schema.py should look like this:

    
import graphene

class Query(graphene.ObjectType):
	pass

class Mutation(graphene.ObjectType):
	pass

schema = graphene.Schema(query=Query, mutation=Mutation)
    
  

We see that these classes are receiving an ObjectType, creating the relationship between the fields in the schema and telling us how to deliver data.


Now let’s register these schemas into our graph/schema.py, so we can pass as many Query and Mutation objects as possible.

    
# graph/schema.py

import player.schema
import games.schema

import graphene

class Query(player.schema.Query, games.schema.Query, graphene.ObjectType):
    pass

class Mutation(player.schema.Mutation, games.schema.Mutation, graphene.ObjectType):
    pass

schema = graphene.Schema(query=Query, mutation=Mutation)
    
  

So far this schema does nothing, but we’re ready to start adding queries and mutations related to your apps. Let’s breathe some life into it!


Types


We previously talked about how graphene-django gives us some abstractions to start working with immediately, and one of these is DjangoObjectType. This allows us to convert our Django model into an object type right out of the box, so let’s create a file called types.py


The player/types.py should look like this:

    
# player/types.py

from graphene_django.types import DjangoObjectType
from django.contrib.auth.models import User
from .models import Player, PlayerBoard

class UserType(DjangoObjectType):
	class Meta:
    	model = User
      exclude = ('password')

class PlayerType(DjangoObjectType):
	class Meta:
    	model = Player
    	fields = ('user', 'picture', 'gender', 'birthday', 'motto', 'board')

class PlayerBoardType(DjangoObjectType):
	class Meta:
    	model = PlayerBoard
    	fields = '__all__'
    
  

The DjangoObjectType by default includes all the fields in the model but we can explicitly add or exclude any fields we want. The fields attribute receives either a dict with the fields or the __all__ string to include all the fields in the model.


To include the relations in the query, you have to include the field name and define that model as a DjangoObjectType subclass. If you ever find yourself wondering why your entities are failing when querying, first check that you have defined the model as DjangoObjectType.


Queries


We have seen how to define our models as types, but how do we map the query to the data? Every time the schema receives a query, it is mapped to a resolver using this pattern:

    
def resolve_foo(self, info, **kwargs):
    
  

foo is the field we’ve defined in the query object, so let’s implement this in our app.


Go to our player/schema.py file and update it with the queries we need.

    
# player/schema.py

import graphene

from .models import Player, PlayerBoard
from .types import PlayerType, PlayerBoardType


class Query(graphene.ObjectType):
	players = graphene.List(PlayerType)
	player = graphene.Field(PlayerType, player_id=graphene.Int())
	player_board = graphene.List(PlayerBoardType)

	def resolve_players(self, info, **kwargs):
    	    return Player.objects.all().order_by('-board__points')

	def resolve_player(self, info, player_id):
    	    return Player.objects.get(pk=player_id)

	def resolve_player_board(self, info, **kwargs):
    	    return PlayerBoard.objects.all().order_by('-points')

schema = graphene.Schema(query=Query)
    
  

Here we can see a couple of interesting things: we enable the queries and map the resolving data. We also have a simple graphene.List; for this, we’re assuming that we won't be receiving any parameters to filter, we just need a list of data. Meanwhile, the graphene.Field makes sure to enable extra fields in the schema.


Now, just like that, our app is available to respond to GraphQL queries. Here we are using the logic directly, but you can include your existing classes to handle the listing of your entities.


Mutations


I wasn’t joking when I mentioned that graphene-django provides us with some powerful abstractions. And while we’re using the graphene mutations to include changes to the data, we can use the CRUD feature that Django offers.


As an example, let’s create in our apps a mutations.py file like this:

    
# player/mutations.py

import graphene

from .types import PlayerType
from .models import Player


class EditPlayerMutation(graphene.Mutation):
	class Arguments:
    	    # The input arguments for this mutation
    	    id = graphene.ID()
    	    picture = graphene.String()
    	    gender = graphene.String()
    	    birthday = graphene.String()
    	    motto = graphene.String()

	# The class attributes define the response of the mutation
	player = graphene.Field(PlayerType)

	def mutate(self, info, id, picture, gender, birthday, motto):
    	    
          player = Player.objects.get(pk=id)
    	    player.picture = picture
    	    player.gender = gender
    	    player.birthday = birthday
    	    player.motto = motto
    	    player.save()
    	    # Notice we return an instance of this mutation
    	    return EditPlayerMutation(player=player)
    
  

In our code, we declare the required arguments to mutate the data. Next, we declare the response from the mutation as we would do with the queries. Finally, we include the logic for the mutation.


Let's wire our mutation into our app schema.py, which should look like this:

    
# player/schema.py

import graphene

from .models import Player, PlayerBoard
from .types import PlayerType, PlayerBoardType
from .mutations import EditPlayerMutation

class Query(graphene.ObjectType):
	players = graphene.List(PlayerType)
	player = graphene.Field(PlayerType, player_id=graphene.Int())
	player_board = graphene.List(PlayerBoardType)

	def resolve_players(self, info, **kwargs):
    	    return Player.objects.all().order_by('-board__points')

	def resolve_player(self, info, player_id):
    	    return Player.objects.get(pk=player_id)

	def resolve_player_board(self, info, **kwargs):
    	    return PlayerBoard.objects.all().order_by('-points')


class Mutation(graphene.ObjectType):
	update_player = graphene.Field(EditPlayerMutation)

schema = graphene.Schema(query=Query, mutation=Mutation)
    
  

We could use the default Django CRUD to handle the mutations, but moving all your logic to this standard seems like a lot of work. Luckily, graphene-django allows us to use our previously created Django Forms or Django REST Serializers. Let’s see how to do this with Django REST, it’s much easier.


Our mutation file should look like this:

    
# player/mutations.py

import graphene
from graphene_django.rest_framework.mutation import SerializerMutation

from .types import PlayerType
from .models import Player
from .serializers import PlayerSerializer


class CreatePlayerMutation(SerializerMutation):
	class Meta:
    	    serializer_class = PlayerSerializer


class EditPlayerMutation(graphene.Mutation):
	class Arguments:
    	    # The input arguments for this mutation
    	    id = graphene.ID()
    	    picture = graphene.String()
    	    gender = graphene.String()
    	    birthday = graphene.String()
    	    motto = graphene.String()

	# The class attributes define the response of the mutation
	player = graphene.Field(PlayerType)

	def mutate(self, info, id, picture, gender, birthday, motto):
    	    player = Player.objects.get(pk=id)
    	    player.picture = picture
    	    player.gender = gender
    	    player.birthday = birthday
    	    player.motto = motto
    	    player.save()
    	    # Notice we return an instance of this mutation
    	    return EditPlayerMutation(player=player)
    
  

Now let’s update our player/schema.py:

    
import graphene

from .models import Player, PlayerBoard
from .types import PlayerType, PlayerBoardType
from .mutations import CreatePlayerMutation, EditPlayerMutation


class Query(graphene.ObjectType):
	players = graphene.List(PlayerType)
	player = graphene.Field(PlayerType, player_id=graphene.Int())
	player_board = graphene.List(PlayerBoardType)

	def resolve_players(self, info, **kwargs):
    	    return Player.objects.all().order_by('-board__points')

	def resolve_player(self, info, player_id):
    	    return Player.objects.get(pk=player_id)

	def resolve_player_board(self, info, **kwargs):
    	    return PlayerBoard.objects.all().order_by('-points')


class Mutation(graphene.ObjectType):
	create_player = graphene.Field(CreatePlayerMutation)
	update_player = graphene.Field(EditPlayerMutation)


schema = graphene.Schema(query=Query, mutation=Mutation)
    
  

And, just like that, we have included our serializer without duplicating any code! The default integration with Django REST enables creations and updates into the model and gives you the ability to override the update queries.


What’s next?


I think we’ve made a lot of progress in our process moving from REST to GraphQL. Already we have a functional graphQL API and we’ve only just begun. In the next part, we will be covering testing, authorization, pagination, and more to complete our integration process, so stay tuned.


---
At FullStack Labs, we are consistently asked for ways to speed up time-to-market and improve project maintainability. We pride ourselves on our ability to push the capabilities of these cutting-edge libraries. Interested in learning more about speeding up development time on your next form project, or improving an existing codebase with forms? Contact us.

Let’s Talk!

We’d love to learn more about your project. Contact us below for a free consultation with our CEO.
Projects start at $50,000.

FullStack Labs
This field is required
This field is required
Type of project
Reason for contact:
How did you hear about us? This field is required