If you're here, you know what GraphQL is. In this tutorial, you'll see how you can use it with Django to set up a single endpoint for all your data, using graphene and graphene_django.
The code written in this tutorial can all be found here, if you wanna take a look.
Prerequisites
To follow this tutorial, you will need:
- A basic understanding of Python
- Basic Django knowledge
Before we start, I know it's frustrating to see me set up the project, because that's not what you came here for. You can skip Step 0 if you want, but I would still recommend reading it.
Step 0 - Setting up the project
Initial setup
First, let's create the project directory and create a virtual environment inside it. I'll call this project task_manager.
$ mkdir task-manager && cd task-manager
$ virtualenv venv -p python3
Activate the environment and install Django.
$ source venv/bin/activate
$ pip install django
Inside the task-manager directory, initialize a new Django project called task_manager
and create a new app called main
. You will use this app for the GraphQL API.
$ django-admin startproject task_manager
$ cd task_manager
$ python manage.py startapp main
Add the main
app to the INSTALLED_APPS
of your project.
...
INSTALLED_APPS = [
'main.apps.MainConfig',
...
]
...
Creating the models
We'll create a schema in which every user can be assigned multiple tasks, but a single task can only have one assignee. It's basically a one-to-many relationship from user to tasks.
Open up main/models.py
and create a Task
model to store the details of a task.
from django.db import models
from django.contrib.auth.models import User
from django.utils import timezone
class Task(models.Model):
assigned_to = models.ForeignKey(User, related_name="tasks", on_delete=models.CASCADE)
title = models.CharField(max_length = 50)
description = models.TextField()
status = models.BooleanField(default=False)
created_on = models.DateTimeField(default=timezone.now)
def __str__(self):
return self.title
Create the migrations and start up the server to make sure everything went well.
$ python manage.py makemigrations main
$ python manage.py migrate
$ python manage.py runserver
Let's create some mock data from the django shell to test our queries. Start the shell with this command.
$ python manage.py shell
Make a few users and a few tasks, for instance -
>> from django.contrib.auth.models import User
>> from main.models import Task
>> User.objects.create_user(username='TestUser1', password='test123')
>> User.objects.create_user(username='TestUser2', password='test123')
>> Task.objects.create(assigned_to_id=1, title='Eat', description='Finish all the greens.')
>> Task.objects.create(assigned_to_id=1, title='Assign', description='Monitor, evaluate and employ')
>> Task.objects.create(assigned_to_id=2, title='Learn', description='If you wish to.')
Step 1 - Configuring the dependencies
We'll be using graphene and graphene_django to implement the GraphQL endpoint.
Why Graphene?
Graphene lets you define your GraphQL schema using application code. This means you do not have to define a separate GraphQL schema and you don't need to explicitly make schema changes whenever your models change. Pretty convenient (though it has it's cons).
Setting up dependencies
Install graphene
and graphene_django
.
$ pip install graphene graphene_django
Add graphene_django
to installed apps of your project, in task_manager/settings.py
...
INSTALLED_APPS = [
'graphene_django',
...
]
...
Step 2 - Writing your first Model Type in GraphQL
Begin by creating a directory main/gql/
.
# Inside task-manager/main
mkdir gql
This directory will contain all the code related to our GraphQL implementation.
The code we write here cannot directly interact with the Django models we earlier defined. So, create a new file inside the gql
directory, called typeDef.py
. This file will contain model types that act as a layer of abstraction between our Django models and our GraphQL implementation.
Let's begin by making a GraphQL Type for the Task
model. Add the following code to a file named main/gql/typeDef.py
from graphene_django.types import DjangoObjectType
from main.models import Task
class TaskType(DjangoObjectType):
class Meta:
model = Task
fields = ('id', 'assigned_to', 'title', 'description', 'status', 'created_on')
DjangoObjectType
maps the model fields to appropriate GraphQL Inside the fields
attribute of the Meta
class, you can specify all the fields on the model that you want to access through GraphQL.
By default, if the fields
are not specified, Graphene exposes all fields of the model. The documentation strongly recommends that you explicitly declare thee fields you want to expose as this prevents unintentional exposure of data.
Step 3 - Writing your first GraphQL Root Type
To enable the client to interact with the model type you need to define root types. GraphQL has 3 root types - queries, mutations and subscriptions. In this tutorial, we will cover only subscriptions.
Defining the queries and resolvers
Queries are used whenever you wish to fetch data from the server. Create a new file called query.py
in the gql
directory. We will define all our queries in this file.
Inside query.py
, add this code.
import graphene
from .typeDef import TaskType
from main.models import Task
class Query:
all_tasks = graphene.List(TaskType)
def resolve_all_tasks(self, info, **kwargs):
return Task.objects.all()
graphene
abstracts the GraphQL types (called scalars) which can be used to define the type of fields. In this case, to tell graphene
that the all_tasks
field should be a list of tasks, we use graphene.List(TaskType)
.
The method resolve_all_tasks
actually performs the query and populates the all_tasks field. Functions like this are called resolvers. You'll need to define resolvers for all the queries you define.
Resolver functions are method of Query class and follow the naming convention resolve_query_name
. They have three arguments:
self
- This is the default parameter of every method in a python class.info
- This has information about the context, user logged In, etc.**kwargs
- Additional information that is send by the client.
Setting up the schema
Now that you're done defining your first query, you can make it accessible from the GraphQL endpoint. To do this, you'll need to add it to the project settings.
Create a file in the task_manager
directory (the one with settings.py
) called schema.py
. In this file, add this code to load Queries from all the apps in our Django Project.
Why are we doing this?
When you have a larger project with multiple apps, having the whole GraphQL schema in a single app can be difficult to maintain. That's why, we define a seperate schema for each app combine each schema inside the project's schema. This way you can seperately develop the schema for each app.
import graphene
from main.gql.query import Query as MainQuery
class Query(MainQuery, graphene.ObjectType):
pass
schema = graphene.Schema(query=Query)
Now load base schema in the project settings by adding this code in settings.py
.
...
GRAPHENE = {
'SCHEMA': 'task_manager.schema.schema',
}
...
Above, we specify that we want Graphene to use the schema
that is located inside task_mager/schema.py
.
In the root urls.py
file, add the GraphQL endpoint.
...
from django.views.decorators.csrf import csrf_exempt
from graphene_django.views import GraphQLView
urlpatterns =[
...
path('graphql/', csrf_exempt(GraphQLView.as_view(graphiql=True)))
...
]
We're using csrf_exempt
here because we're just testing things out.
Now save all the changes you have made and run server.
$ python manage.py runserver
Step 4 - Using GraphiQL to make your first query
Visit 127.0.0.1:8000/graphql
from your browser and you should see a GraphiQL playground which you will use to send request and get responses, it is a great tool as it automatically documents the project and tells about all the queries, mutations and subscriptions that the server supports.
If you click on the docs tab on the right-hand side you will find that there is a ROOT TYPES
heading which lists query. If you click on it, it will list all the queries, presently you should have just one 'allTasks' which is specified as an array of 'TaskType'. If you click on this it lists all the fields and their types which can be demanded from the server like shown below.

Notice how your query is named allTasks
even though you defined it as all_tasks
. Graphene converts snake_case
to snakeCase
to meet the GraphQL standards.
If you take a closer look, you will notice that there is no assigned_to
field in TaskType
even-though it is present in models, this is because we have not yet defined UserType and that is the scalar of assigned_to
field, I'll be coming back to this once we define UserType.
Try running this query now.
query {
allTasks {
title
description
}
}

As you can see, the server responds with only the title
and description
fields for each query.
Step 5 - Adding more queries
Now that you know how to get a list of all tasks, lets see how you can get individual tasks by their ID.
Inside the Query
class in query.py
, add this code.
...
from django.core.exceptions import ObjectDoesNotExist
class Query:
...
task = graphene.Field(TaskType, id=graphene.ID())
...
def resolve_task(self, info, **kwargs):
try:
return Task.objets.get(pk=kwargs.get('id'))
except ObjectDoesNotExist:
return None
Here, graphene.Field(TaskType, id=graphene.ID())
does the following two things
- The first argument i.e.
TaskType
tells graphene that the fieldtask
should resolve to a singleTaskType
instance (which just means that when you use thetask
query, you'll get back a single task). - The second argument i.e.
id=graphene.ID()
tells graphene that the client is going to provide a variable calledid
of typegraphene.ID()
in the arguments. Thisid
will can be accessed in the resolver for this field to fetch data accordingly. In this case, we will use thisid
to fetch aTask
with the same id.
Inside our resolver, we use the id (which is available to us in the kwargs
argument) to fetch the appropriate Task
with that id. Querying the database with get
throws an error in case a task with that ID does not exist so we wrap the query with a try-except block.
Try out this query. If all goes well, you should something like the result shown in the image below.
query {
task (id:1) {
title
status
}
}

Note that the GraphQL server returns a status code of 200 OK even when it returns a error so make sure you check for errors field in data when working on the client-side of the project.
So now you can do the following -
- Fetch all tasks
- Fetch a task by id
You can similarly add more queries. You can pass any kind of variables in your queries as additional arguments (just like we passed id
), so you can have pretty much any kind of query this way.
Step 6 - Writing Queries for Users
Let's go over all that again to recap what you learnt. This time, we'll add queries for the User
model. We'll be using the default User Model that comes with Django for this.
Start by adding another type - UserType
. In the typeDef.py
file, add the following code.
...
from django.contrib.auth.models import User
...
class UserType(DjangoObjectType):
class Meta:
model = User
fields = ('id', 'username', 'password', 'task')
Save the changes and run the server.
$ python manage.py runserver
Take a look at the fields available with TaskType
it now has assignedTo field because we are past defining the UserType
. How did graphene know which type assignedTo
maps to? Well that's some django style magic right there.
Here's what happened: In TaskType
, you set model = Task
. In the Task
model, assigned_to
is a foreign key to User
. The User
model is then used in UserType
. So, django_graphql
did all that detective work for you and set the assignedTo
field to UserType
.

The UserType
serves as an abstraction layer over the default User
Model. The default model comes with a whole set of fields but we will be interacting with only 4 of them so we have exposed only those fields in the GraphQL schema.
Now add a query for allUsers and userById.
...
from .typeDefs import UserType
from django.contrib.auth.models import User
class Query:
...
all_users = graphene.List(UserType)
user = graphene.Field(UserType, id=graphene.Int())
...
def resolve_all_users(self, info, **kwargs):
return User.objects.all()
def resolve_user(self, info, **kwargs):
try:
return User.objects.get(pk=kwargs.get('id'))
except ObjectDoesNotExist:
return None
If you save changes, run server and have a look at docs tabs it will show the 2 new queries.

Now, you can make queries any way you wish. For example, try running the following query -
allTasks {
assignedTo {
id
task {
title
}
}
}

This query will list all tasks, inside every task will be the user to whom the task is assigned. Furthermore, inside every user there will be all the tasks assigned to that user. This query is redundant, but it shows how powerful GraphQL is. We basically got users from tasks, and then got the tasks from users again. In technical speak, you can traverse your model-relationship graph in any way you want.
Conclusion
If you've managed to follow this through, you should be having a Task Manager App capable of reading tasks and users using GraphQL endpoint, at your deposition. In the next tutorial we'll see how to write about writing mutations that can be used to create, update and delete tasks and users.
Once your GraphQL API is ready, the client side developer can truly fetch any data in any desired format, in a single API call. That's powerful.
All criticism and questions are welcome. If any of the above doesn't work: file an issue at the repository, or leave a comment below.