The Proper Way to Start a Django Project With GraphQL (Part 2)

Creating a simple to-do app with authentication — Queries and mutations

Cansın Yıldız
May 26 · 7 min read
Image for post
Image for post
Photo by Ilya Pavlov on Unsplash

In the first article of this series, we talked about the proper way to start a Django and Graphene project from scratch. Today, we will focus on creating GraphQL endpoints. This article assumes you have a basic application up and running. If not, feel free to check out the first article.

GraphQL is a powerful language for querying the exact data you need from your back end. Unlike with its predecessor REST, with GraphQL APIs you end up with a single endpoint (commonly called /api/graphql/ or simply /graphql/), to query the needed data by the client in bulk. Similarly, one can also send multiple mutations to manipulate the data or trigger an event on the back end. In addition to queries for fetching and mutations for manipulation, there are also subscriptions that enable the server to push data to the clients. But that is a story for another time.

For now, let’s focus on how we can create a simple CRUD (create, read, update, and delete) back end with GraphQL. As most would agree, the best way to get started learning a new technology is just to get our hands dirty. For the rest of this article, we will code through a GraphQL back end for a simple application.

Enter TodoMVC!

When you start learning a new programming language, the first thing you would do is to write a program that prints out “Hello World.” Similarly, when you start learning a new web stack, the first thing you should do is to write a to-do app. This is such a prevailing idea that there is a very well-known project called TodoMVC that lists a bunch of reference implementations of the same to-do app with different frameworks.

Image for post
Image for post

Above is what the application looks like in the wild. Today’s goal is to write the necessary backend code to support such an application. As you can see, the user can create to-do items, delete them, mark them as complete (or incomplete), and filter the to-do list by their completion status. There is also a bulk operation, “clear completed”pretty straightforward stuff. Alright, let’s dive into the code, shall we?

First Things First: A New App and Data Model

We will start the code by creating a new Django application. A Django application is a somewhat encapsulated group of code within a Django project that shares a certain commonality. A typical Django application would usually end up having tens, if not hundreds, of small applications. As our app will be concerned with to-dos, so we will call it to-dos. How convenient!

$ cd mybackend
mybackend $ workon mybackend
(mybackend) mybackend $ cd mybackend
(mybackend) mybackend/mybackend $ django-admin startapp todos
(mybackend) mybackend/mybackend $ cd todos
(mybackend) mybackend/mybackend/todos $ ls -l
__init__.py
admin.py
apps.py
migrations
models.py
tests.py
views.py

As you can see, the startapp command of django-admin created an application, and filled the application folder with the necessary files. One slightly confusing thing to realize here is that we created our app within the mybackend module we had inside the root of the project, rather than putting the application directly inside the root. This has the advantage of making sure your application modules are namespaced with your application module’s name (in this case, “mybackend”). We then need to edit app.py and update its name and label accordingly:

We also need to add this new application to our INSTALLED_APPS list at settings.py:

Now that we have the basic setup for the application ready, we can go ahead and start thinking about the data model we need. Luckily, our example app is exceptionally straightforward. We just need a single model to capture to-dos, with a text field for the actual text and a boolean field for marking a to-do as complete. While we are there, we could also add some information around created and modified time stamps. Oh, and obviously, we will need a unique primary key for each entry in our database.

There is a pretty nice library called django-model-utils that gives us useful model mixins and utilities to use in our application. We will first install it via:

(mybackend) mybackend $ pip install django-model-utils
(mybackend) mybackend $ pip freeze > requirements.txt

Ok, we are ready to implement our model, at models.py:

As you can see, we have a TextField called text for storing the to-do text, a BooleanField called completed to store the completion information (which defaults to False ). We also extended from two mixins: TimeStampedModel and UUIDModel provided by model_utils (aka django-model-utils ). As you may guess, TimeStampedModel mixin adds two DateTimeFields to capture the created and modified time stamps. And the UUIDModel mixin adds a UUID-based primary key to our model.

Alright, the model code is done. Now we need to create these models on the database. To do so, use the following:

(mybackend) mybackend $ python manage.py makemigrations
(mybackend) mybackend $ python manage.py migrate

These commands would first generate a migration file that describes the model we just created and then apply the migration against the database. You should commit the migration files to your Git repository.

While we are here, we might as well also enable a simple admin interface for admins to fiddle with the to-dos if they see the need. For that, we simply edit our admin.py:

Now that we have our first ever admin page let’s see it in action. First, we’d need to create a superuser:

(mybackend) mybackend $ python manage.py createsuperuser

Run the above command and follow the prompts. Then you can run python manage.py runserver and log in to http://127.0.0.1:8000/admin/ with the credentials you just created. You should see a link to Todos.”

GraphQL Query and Mutations

It’s time to expose this model to the outside world (aka your clients). For that, we will create three more files under mybackend/mybackend/todos/, namely, mutations.py, queries.py, and types.py . As the names suggest, mutations.py is the module to write our mutations, queries.py is for queries, and types.py is for the object types we are returning from our GraphQL endpoint.

Let’s start with the types. Generally speaking, you would end up with a type for each model you have. In our case:

It’s as simple as that, thanks to Graphene’s Django integration. The DjangoObjectType simply expects a model meta that points to our actual model class (that is, Todo) and generates the corresponding type.

Now that we have the type in place, we can go ahead and create the queries that would return to-do items. We will deal with the mutations to manipulate these items after that. At our queries.py:

As you can see, we are exposing two queries: todos and todo. Thetodos query returns a list of to-do items. And it accepts an argument completed to filter to-do items by completion status. Thetodo query, on the other hand, returns a single to-do item, given its id.

Now we need to register these queries to our schema. To do so, we would need to update our schema.py as:

Pretty straightforward, right? We just created a new Query class that extends from our Query class at thetodo application. This new top-level Query will extend from every applications’ Query going forward. If you want, you can now head back to your admin interface for to-do items and add a few to-dos. Then visit http://127.0.0.1:8000/graphql and run the below query (by typing it on the left-hand side and hitting the Play button):

{
todos{
text
completed
}
}

You should see the query result as a JSON object on the right-hand side.

Congratulations! You have just run your first ever GraphQL query on the back end you created.

Now that the queries out of the way, we can start working on the mutations. At the minimum, we’d need to be able to create to-dos, update to-dos (for marking them as complete), and delete to-dos. Go ahead and plug the below code into your mutations.py:

I think the code here is quite self-explanatory. But to briefly describe it, each class corresponds to a single mutation. At their Arguments sub-class, we are defining the arguments that mutation is expecting to receive. And at their mutate function, we are implementing the behavior we need. The arguments set are passed to the mutate function as positional arguments, as you can see at UpdateTodo and DeleteTodo. And the second positional argument info holds some metadata regarding the mutation, including the context (but more on that later). These mutation classes are then registered to a top-level Mutation, where each field’s name corresponds to the mutation’s exposed name to the GraphQL interface. The Graphene conveniently even auto-camel-cases those field names for us.

Before we can test these new mutations we wrote, we just need to register them on our schema. Similarly to how we registered the queries, we will update the schema.py file:

Now, if you head back to http://127.0.0.1:8000/graphql and run the following, you should see the result of your first successful mutation:

mutation {
createTodo(text:"A new to-do item.", completed:false) {
todo {
id
text
completed
created
modified
}
}
}

How exciting!

If you paid attention, you will have realized we did not implement the bulk operation “clear completed”. I will leave that as an exercise for you to figure out.

Wrapping Up

This was a simple example of creating GraphQL interfaces using Django and Graphene. But the general ideas and patterns you learned here hold, no matter how large or complex your interface gets.

In the next article of this series, we will talk about how we can enable authentication on our GraphQL APIs with the help of JSON Web Tokens. Make sure to subscribe not to miss out!

Better Programming

Advice for programmers.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store