FOR DEVELOPERS

GraphQL With Python (Django): A Complete Guide

GraphQL with Python Django A Complete Guide

GraphQL is gaining popularity and many consider it a successor to REST APIs. If you're familiar with Python and Django, and want to get started with GraphQL or just to get a taste, this guide is definitively for you. You're going to learn more about how to use GraphQL with Python and Django to build a simple CRUD API for a notes app (like google keep). That tutorial assumes that you're already familiar with Python and Django.

This article will help in developing your knowledge and prepare you for any Django jobs interview!

What is GraphQL?

GraphQL is a data query and manipulation language for APIs, and a runtime for executing queries using a type system you define for your data.
It allows retrieving exactly what data we need in a nested, convenient, and highly flexible way. It also works with only one URL to perform all actions.
To have a glance, suppose you want to fetch an article and a paginated list of comments using REST, you will probably make two requests:

1. get to "/article/<article-id>" to retrieve the article:

json
{
    "id": 5,
    "preview": "Blah blah blah",
    "fullContent": "Full Blah blah blah",
}

2. get "/comments/<article-id>/?page=1" to retrieve comments:

json
{
    "id": 5,
    "content": "Comment here,"
}

But with GraphQL you can just do this

gql
query {
    article (articleId) {
        id
        preview
        fullContent
        comments(PaginationInfosHere) {
            id
            content
        }
    }
}

Now let's suppose you want to render only the preview of each article on some pages and the full content and comments on another page. You just can't do it using REST, you're always obliged to get all the data a REST API exposes to you.

Here is the beauty and flexibility of GraphQL. You could fetch exactly what you need:

gql
query {
    article (articleId) {
        id
        preview
    }
}

And all that using the same URL.
You can also go as deep as you want to retrieve exactly what you need as data.

GraphQL vs REST: A deep comparison

Many developers believe that GraphQL will replace REST. Many others have already discovered that GraphQL helps solve some of the common challenges developers face when creating REST APIs.

However, deciding to opt for GraphQL comes with several considerations. GraphQL offers several advantages that make it a compelling option for API development.

Data fetching

One of the key benefits is its ability to overcome the limitations of REST about data fetching. In REST, it is common for clients to either receive more data than they need (overfetching) or not enough data (underfetching) because the only way to download data is by accessing pre-defined endpoints that return fixed data sets. GraphQL allows clients to specify the exact data they want in a single request, making it easier to design APIs that meet their specific needs.

Schema and type safety

GraphQL uses a type system to define the capabilities of an API. The types exposed in an API are defined in a schema using the GraphQL Schema Definition Language (SDL) and/or through code. This allows for strong typing and a clear understanding of the data that can be queried from the API.

Frontend teams can use a typed GraphQL API with confidence, knowing that if the backend team makes any changes to the API design, they will receive instant feedback when a query is raised from the frontend. This enables better collaboration and ensures that the frontend and backend are always in sync.

Tools like the GraphQL Code Generator can also automatically generate code for queries and mutations based on the GraphQL query files in the codebase, which speeds up development and reduces the risk of errors in production.

Fast product development

A common pattern with REST APIs is to structure the endpoints according to the different views in the app (e.g. /menu, /prices, /images, etc.). This is convenient because it allows the client to retrieve all the necessary data for a given view by accessing the corresponding endpoint. However, this approach can be inflexible and hinder rapid iteration. Every time the UI is changed, there is a risk that the data requirements will change, requiring the backend to be adjusted as well. This can be time-consuming and slow down product development.

In contrast, the flexible nature of GraphQL enables changes on the client side to be made without any extra work on the server. Since clients can specify their exact data requirements, no backend adjustments are needed when the frontend design or data needs changes.

Implementation of the schemas

One key difference between GraphQL and other API technologies is its ability to stitch multiple schemas into a single schema that can be accessed by the client. This allows for a more flexible and modular approach to API design and implementation. You can combine multiple independent schemas into a unified API without having to write custom code or make other complex changes. This simplifies the API development process and makes it easier to manage and maintain.

The main difference between GraphQL and REST APIs is that GraphQL is a specification and query language, while REST is an architectural concept for network-based software.

Now, let's put our hands to work using this theory!

GraphQL tutorial practice

First, let’s present what we call in graphQL jargon the 'schema'. It’s just the set of the different components of a GraphQL API:

  • types that define the data we manipulate on our API.
  • queries are used to fetch information about the types.
  • mutations to update the types.
  • subscriptions to listen to events or types changes, a bit like a WebSockets but we won't cover it here.

The common package to create a GraphQL API with Python and Django is graphene.

  • Create and start a virtual environment

I'm going to use virtualenv but you can go with what you're comfortable with,
poetry or conda or whatever tool it is:

bash
virtualenv venv
source venv/bin/activate
  • Install django
bash
pip install django
  • Create the django project
bash
django-admin startproject graphql_notes_api
bash
cd graphql_notes_api
  • create our working app
bash
django-admin startapp notes
  • Install graphene
bash
pip install graphene-django
  • add "graphene_django" and "notes" app to "INSTALLED_APPS"
python
INSTALLED_APPS = [
    ...
    'django.contrib.staticfiles',
    # Add lines below
    "notes",
    "graphene_django" 
]
  • add a GraphQL URL to the urls.py of your Django project:
python
...
from django.views.decorators.csrf import csrf_exempt # Add this line
from graphene_django.views import GraphQLView # Add this line

urlpatterns = [ path('admin/', admin.site.urls), path("graphql", csrf_exempt(GraphQLView.as_view(graphiql=True))), # Add this line ]

"graphiql=True" is set to "True" because we want to use "graphiql" (an internal
web interface to test the GraphQL API).

  • add Note and Category models
python

graphql_notes_api/notes/models.py

from django.db import models

class Category(models.Model): name = models.CharField(max_length=100)

def __str__(self):
    return self.name

class Note(models.Model): content = models.TextField() category = models.ForeignKey( Category, related_name="notes", on_delete=models.CASCADE )

def __str__(self):
    return self.content[:10] + &#39;...&#39; if len(self.content) &gt; 10 else &#39;&#39;</pre><ul><li>create migrations</li></ul><pre>bash

python manage.py makemigrations

  • run migrations
bash
python manage.py migrate
  • to make it easy, load some fixtures.

Create this file "graphql_notes_api/notes/fixtures/notes.json"

json
[
{
"fields": {
"name": "Personal"
},
"model": "notes.category",
"pk": 1
},
{
"fields": {
"name": "Work"
},
"model": "notes.category",

  &quot;pk&quot;: 2
},
{
  &quot;fields&quot;: {
    &quot;category&quot;: 1,
    &quot;content&quot;: &quot;I like good old eggs&quot;
  },
  &quot;model&quot;: &quot;notes.note&quot;,
  &quot;pk&quot;: 1
},
{
  &quot;fields&quot;: {
    &quot;category&quot;: 1,
    &quot;content&quot;: &quot;I have a yoga session tomorrow&quot;
  },
  &quot;model&quot;: &quot;notes.note&quot;,
  &quot;pk&quot;: 2
},
{
  &quot;fields&quot;: {
    &quot;category&quot;: 2,
    &quot;content&quot;: &quot;I have a meeting with my boss next friday&quot;
  },
  &quot;model&quot;: &quot;notes.note&quot;,
  &quot;pk&quot;: 3
},
{
  &quot;fields&quot;: {
    &quot;category&quot;: 2,
    &quot;content&quot;: &quot;We need to release the MVP before June.&quot;
  },
  &quot;model&quot;: &quot;notes.note&quot;,
  &quot;pk&quot;: 4
}

]

  • load the fixtures
bash

python manage.py loaddata notes

Installed 6 object(s) from 1 fixture(s)

Now we are moving to the interesting part. We are going to set up the schema:

  • create "graphql_notes_api/schema.py" file and add the following code in it:
python

graphql_notes_api/schema.py

import graphene from graphene_django import DjangoObjectType

from notes.models import Category, Note

class CategoryType(DjangoObjectType): class Meta: model = Category fields = ("id", "name")

class NoteType(DjangoObjectType): class Meta: model = Note fields = ("id", "name", "content")

class Query(graphene.ObjectType): all_notes = g:aphene.List(NoteType) category_by_name = graphene.Field(CategoryType, name=graphene.String(required=True))

def resolve_all_notes(root, info):
    return Note.objects.select_related(&quot;category&quot;).all()

def resolve_category_by_name(root, info, name):
    try:
        return Category.objects.get(name=name)
    except Category.DoesNotExist:
        raise Exception(&#39;Invalid category Id&#39;)

schema = graphene.Schema(query=Query)

  • define the GRAPHENE config in graphql_notes_api/settings.py:
bash

graphql_notes_api/settings.py

GRAPHENE = { "SCHEMA": "schema.schema" }

And we are done, and no, I'm not joking, our graphQL API is ready :relieved:.
Let's test it out.

  • Start the django server
bash

python manage.py runserver

Watching for file changes with StatReloader Performing system checks...

System check identified no issues (0 silenced). December 12, 2022 - 21:39:16 Django version 3.2.16, using settings 'graphql_notes_api.settings' Starting development server at http://127.0.0.1:8000/ Quit the server with CONTROL-C.

  • Go to "localhost:8000/graphql" to run our first query!

You should see an interface like that:

GraphQL interface.webp

  • Execute
gql
query {
allNotes {
id
content
}
}

You should have as output:

gql
{
"data": {
"allNotes": [
{
"id": "1",
"content": "I like good old eggs"
},
{
"id": "2",
"content": "I have a yoga session tomorrow"
},
{
"id": "3",
"content": "I have a meeting with my boss next friday"
},
{
"id": "4",
"content": "We need to release the MVP before June."
}
]
}
}

Nice, you have just created a working graphQL API!

  • Querying relations

We can also query for relations. And that is also a power of graphQL.

For example, we can list all notes related to a given category like this:

gql
query {
categoryByName(name: "Personal") {
id
name
notes {
id
content
}
}
}

And here should be the output:

gql
{
"data": {
"categoryByName": {
"id": "1",
"name": "Personal",
"notes": [
{
"id": "1",
"content": "I like good old eggs"
},
{
"id": "2",
"content": "I have a yoga session tomorrow"
}
]
}
}
}
  • Now, let's add mutations to create and remove notes

Add the following code to "schema" file

python

graphql_notes_api/schema.py

from graphql import GraphQLError # add this line ... class NoteCreationMutation(graphene.Mutation): class Arguments: # The input arguments for this mutation content = graphene.String(required=True) category_id = graphene.ID(required=True)

# This exposes the created `note` in the response to the mutation
note = graphene.Field(NoteType)

@classmethod
def mutate(cls, root, info, content, category_id):
    # We first make sure that the Id is valid
    try:
        category=Category.objects.get(id=category_id)
    except Category.DoesNotExist:
        raise Exception(&#39;Invalid category Id&#39;)

    note = Note.objects.create(content=content, category=category)
    # Notice we return an instance of `Note` because the mutation expose
    # a `NoteType` in the response as seen above.
    return NoteCreationMutation(note=note)

class NoteDeletionMutation(graphene.Mutation): class Arguments: note_id = graphene.ID(required=True)

# This exposes a boolean to know the status of the deletion.
deleted = graphene.Boolean(default_value=False)

@classmethod
def mutate(cls, root, info, note_id):
    try:
        note=Note.objects.get(id=note_id)
    except Note.DoesNotExist:
        raise Exception(&#39;Invalid note Id&#39;)
    note.delete()
    return NoteDeletionMutation(deleted=True)

class Mutation(graphene.ObjectType): create_note = NoteCreationMutation.Field() delete_note = NoteDeletionMutation.Field()

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

Note that we defined the "Mutation" root class and added it the schema "mutation=Mutation" right after the query.

  • We can test our mutation.

Go back on graphiql and run:

gql
mutation{
createNote(categoryId:1, content: "My first real note") {
note {
id
content
category {
id
name
}
}
}
}

You should have:

gql
{
"data": {
"createNote": {
"note": {
"id": "5",
"content": "My first real note",
"category": {
"id": "1",
"name": "Personal"
}
}
}
}
}

Run again the query and you will notice that our new note has been created.

  • Delete the note

Just run the following mutation

gql
mutation {
deleteNote(noteId: 5) {
deleted
}
}

In our case, the newly created note has an Id of 5.

You should get:

gql
{
"data": {
"deleteNote": {
"deleted": true
}
}
}

Handle pagination and filtering with Graphene Relay

The last thing we want to show here is graphene relay.

Relay is a JavaScript framework designed to improve the performance, maintainability, and type safety of GraphQL data management. It was initially made for React applications. We want to show you one of the most important uses of relay for building API: pagination.

We could have handled the pagination manually by defining the page count and offset in the query and using it in the resolver, but that would have been less elegant as relay does it automatically and in a much cleaner way.

Let start:

  • install django-filter
bash
pip install django-filter
  • Update the schema file
python

graphql_notes_api/schema.py

Add these imports at the top of the file

from graphene import relay, ObjectType from graphene_django import DjangoObjectType from graphene_django.filter import DjangoFilterConnectionField ... ...

Graphene will automatically do a mapping between Category model's fields onto

CategoryNode type.

The configuration is made in the Meta class below

class CategoryNode(DjangoObjectType): class Meta: model = Category filter_fields = ['name'] interfaces = (relay.Node, )

class NoteNode(DjangoObjectType): class Meta: model = Note # Setup more advanced filtering here filter_fields = { 'content': ['exact', 'icontains', 'istartswith'], 'category': ['exact'], 'category__name': ['exact'], } interfaces = (relay.Node, ) ... ... class Query(graphene.ObjectType): ... # Add the lines below category = relay.Node.Field(CategoryNode) all_categories_relay = DjangoFilterConnectionField(CategoryNode)

note = relay.Node.Field(NoteNode)
all_notes_relay = DjangoFilterConnectionField(NoteNode)</pre><p>That&#39;s all. Let&#39;s test it:</p><ul><li>Run</li></ul><pre>gql

query { allNotesRelay { edges { node { id, content } } } }

You should have this output:

{
"data": {
"allNotesRelay": {
"edges": [
{
"node": {
"id": "Tm90ZU5vZGU6MQ==",
"content": "I like good old eggs"
}
},
{
"node": {
"id": "Tm90ZU5vZGU6Mg==",
"content": "I have a yoga session tomorrow"
}
},
{
"node": {
"id": "Tm90ZU5vZGU6Mw==",
"content": "I have a meeting with my boss next friday"
}
},
{
"node": {
"id": "Tm90ZU5vZGU6NA==",
"content": "We need to release the MVP before June."
}
}
]
}
}
}

Note: The "id" present in each node is auto-generated by the relay and is unique across all objects.

Now we can specify a custom "id"

gql
query {
note(id: "Tm90ZU5vZGU6Mg==") {
content
}
}

You'll have this output:

{
"data": {
"note": {
"content": "I have a yoga session tomorrow"
}
}
}
  • And lastly, let's test the filtering capability.
gql
query {

You can also use category: &quot;CATEGORY GLOBAL ID&quot;

allNotesRelay(content_Icontains: "yoga", category_Name: "Personal") { edges { node { content } } } }

Output:

{
"data": {
"allNotesRelay": {
"edges": [
{
"node": {
"content": "I have a yoga session tomorrow"
}
}
]
}
}
}

Final congratulations, we are at the end of this guide.

Don't hesitate to learn more about GraphQL on the official documentation and start using it to build awesome projects.

Conclusion

GraphQL allows you to build any kind of API, and being more flexible than REST, it lets you fetch exactly what you want using the same URL. But it comes with the price of being a little more complex than REST. However, in this tutorial, we have shown how Python and Django makes it easier to implement GraphQL. We started by explaining what GraphQL is, then compared it to REST, and finally created a simple GraphQL API that covers core GraphQL topics like types, queries, mutations, relay using the Django Framework and graphene.

Author

  • GraphQL With Python (Django): A Complete Guide

    Horace Folahan FAYOMI

    Software engineer with more than 3 years of experience in FullStack web development and a technical writer in my spare time.I have relevant experience working on Python, Django, React, VueJS, GraphQL.

Frequently Asked Questions

GraphQL allows to specify and retrieve the exact data needed from the server which will improve speed. It allows accessing the API using a single endpoint reducing network latency.

You need a Python backend or Full Stack web framework like Django that either has GraphQL integrated or has an additional module to support GraphQL APIs. In the tutorial, we used Django as a framework and graphene as an additional module. But there are others frameworks that you can use like FastAPI or Flask.

There is no best language for GraphQL as it is a specification implemented in several languages. So the language will depend on the frameworks and the tools used for development. For instance, if the project has many real-time core features, Nodejs will be a better choice than Python for the backend framework, and then the language will be JavaScript instead of Python.

Yes, because compared to REST, where you can retrieve all the information of the schema using GET by default, GraphQL helps the client to specify and retrieve exactly what is requesting as data.

If you are using Django, you just need to create a Django project, setup an app, add graphene module, create a query class, and add it to the "schema".

You can install (using pip or poetry) and use any Python GraphQL clients like python-graphql-client - PyPI and just follow the straightforward documentation.

View more FAQs
Press

Press

What’s up with Turing? Get the latest news about us here.
Blog

Blog

Know more about remote work. Checkout our blog here.
Contact

Contact

Have any questions? We’d love to hear from you.

Hire remote developers

Tell us the skills you need and we'll find the best developer for you in days, not weeks.